JWT详解
【参考文章JWT详解】
【参考Java使用JWT进行登录校验】
JWT是什么
JWT全称:Json Web Token
在介绍JWT之前,我们先来回顾一下利用token进行用户身份验证的流程:
- 客户端使用用户名和密码请求登录
- 服务端收到请求,验证用户名和密码
- 验证成功后,服务端会签发一个token,再把这个token返回给客户端
- 客户端收到token后可以把它存储起来,比如放到cookie中
- 客户端每次向服务端请求资源时需要携带服务端签发的token,可以在cookie或者header中携带
- 服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求数据
而JWT就是上述流程当中token的一种具体实现方式,其全称是JSON Web Token
官网地址:https://jwt.io/

JWT组成结构
JWT本质上就是一个字符串,由三部分组成,通过.将各部分连接成一个字符串;
类似 aaaa.bbbb.cccc,其中 aaaa表示头部(Header),bbbb表示载荷(Payload),cccc为签名(Signature)

Header
JWT头是一个描述JWT元数据的JSON对象
- alg属性表示【签名使用的算法】,默认为HMAC SHA256(写为HS256);
- typ属性表示令牌的类型,JWT令牌统一写为JWT。
最后,使用Base64 URL算法将上述JSON对象转换为字符串保存
{
"alg": "HS256",
"typ": "JWT"
}
Payload
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据(不含敏感数据如电话、userId、密码等)。 JWT指定了七个【默认】字段供选择
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
例如:
{
"sub": "1234567890",
"name": "Helen",
"admin": true
}
注意,默认情况下JWT是未加密的,因为只是采用base64算法进行编码,拿到JWT字符串后可以转换回原本的JSON数据,任何人都可以解读其内容;
因此不要构建隐私信息字段,比如用户的密码一定不能保存到JWT中,以防止信息泄露。JWT只是适合在网络中传输一些【非敏感】的信息
Signature
签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希(该过程不可逆),以确保数据不会被篡改。
- 首先,需要指定一个密钥(secret)。该密码仅仅为【保存在服务器】中,并且不能向用户公开。
- 然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
在计算出签名哈希后,JWT头,有效载荷、签名哈希的三个部分组合成一个字符串,每个部分用.分隔,就构成整个JWT对象
JWT验证
- 客户端需要请求服务器后端接口时,发送JWT给服务器
- 服务器拿到JWT后,解析JWT字符串,拿出Payload
- 然后根据自己内部保存的密钥secret,对Payload进行求哈希
- 最后判断生成的签名与客户端发来的JWT中的签名是否一致,是则说明传输过程中Payload没用被篡改,否则说明数据被篡改。
Java中使用JWT
官网推荐了6个Java使用JWT的开源库,其中比较推荐使用的是java-jwt和jjwt-root
java-jwt
引入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
生成JWT
public class JWTTest {
@Test
public void testGenerateToken(){
// 指定token过期时间为10秒
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, 10);
String token = JWT.create()
.withHeader(new HashMap<>()) // Header
.withClaim("userId", 20) // Payload
.withClaim("userName", "ajream")
.withExpiresAt(calendar.getTime()) // 过期时间
.sign(Algorithm.HMAC256("fjareuw")); // 签名用的密钥secret
System.out.println(token);
}
}
解析JWT
public void testResolveToken(){
// 创建解析对象,使用的算法和secret要与创建token时保持一致
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("fjareuw")).build();
// 解析指定的token
DecodedJWT decodedJWT = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6ImJhb2JhbyIsImV4cCI6MTU5OTkyMjUyOCwidXNlcklkIjoyMX0.YhA3kh9KZOAb7om1C7o3vBhYp0f61mhQWWOoCrrhqvo");
// 获取解析后的token中的payload信息
Claim userId = decodedJWT.getClaim("userId");
Claim userName = decodedJWT.getClaim("userName");
System.out.println(userId.asInt());
System.out.println(userName.asString());
// 输出超时时间
System.out.println(decodedJWT.getExpiresAt());
}
实际开发中使用JWT
在实际的SpringBoot项目中,一般我们可以用如下流程做登录:
- 生成payload:在登录验证通过后,拿到前端用户名和密码,然后生成一个随机的字符串作为【密钥key】的一部分,用户信息作为value存入Redis,并设置过期时间,这个过期时间就是登录失效的时间
- 生成JWT:根据第一步生成的payload生成JWT并返回给前端用户,前端把JWT保存下来。
- 前端之后每次请求都在请求头中的【Authorization字段】中携带JWT字符串
- 后端定义一个拦截器,每次收到前端请求时,都先从请求头中的Authorization字段中取出JWT字符串并进行验证,验证通过后解析出payload,获取用户信息,如果能获取到就说明用户已经登录
JWT登录拦截器:
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String JWT = request.getHeader("Authorization");
try {
// 1.校验JWT字符串
DecodedJWT decodedJWT = JWTUtils.decode(JWT);
// 2.取出JWT字符串载荷中的随机token,从Redis中获取用户信息
...
return true;
}catch (SignatureVerificationException e){
System.out.println("无效签名");
e.printStackTrace();
}catch (TokenExpiredException e){
System.out.println("token已经过期");
e.printStackTrace();
}catch (AlgorithmMismatchException e){
System.out.println("算法不一致");
e.printStackTrace();
}catch (Exception e){
System.out.println("token无效");
e.printStackTrace();
}
return false;
}
}
本文来自博客园,作者:aJream,转载请记得标明出处:https://www.cnblogs.com/ajream/p/16275057.html

JWT,可用于Java登录校验
浙公网安备 33010602011771号