本篇来了解下JWT令牌相关的知识,和传统JSESSIONID相比,差别还是很大的,一起来看看吧。
JWT令牌简介
- 全称:Json Web Token
- 特点:
自包含
,即自身包含了一些用户信息,和以前的随机串(JSSESSIONID)相比,以前JSSESSIONID方式缓存丢失后,登录信息就丢失了,而JWT可以通过自身包含的信息重新查出用户数据。密签
,加salt进行签名,防止别人修改。可扩展
,想放什么信息都可以放,但是一般不放敏感信息,因为只要有令牌都是可以查看里面信息的。
- 应用场景,可以作为接口调用的
凭证
,可以很方便的实现SSO单点登录
(下一篇文章分享)。
JWT的构成
- 首先JWT分为三部分,举例如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjk1NjJfRkVFREJBQ0siLCJleHAiOjE1OTQ5NzQxNjIsImJlbG9uZ1RvSWQiOjEsImlhdCI6MTU5MjM4MjE2Mn0.wrsn8OiDBAvu8uHhIR01ns2qnP6y-SE0ziEjb8wjxUA
头部(header)
- 头部有两部分信息,一个是
声明类型
,这里是JWT;一个是声明加密的算法
,一般使用HMAC/SHA256算法。 - 举例如下:
{
'typ': 'JWT',
'alg': 'HS256'
}
- 然后将头部进行base64加密(该加密是可以对称解密的),构成了JWT第一部分:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
载荷(payload)
- 载荷就是
存放有效信息
的地方。包括标准中注册的声明、公共的声明、私有的声明。 - 标准中注册的声明包含:
- iss: jwt签发者
- sub: jwt所面向的用户
- aud: 接收jwt的一方
- exp: jwt的过期时间,这个过期时间必须要大于签发时间
- nbf: 定义在什么时间之前,该jwt都是不可用的.
- iat: jwt的签发时间
- jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
- 公共声明:公共的声明可以添加任何信息,一般添加
用户的相关信息或其他业务需要的必要信息
。不建议添加敏感信息,因为该部分在客户端可解密。 - 私有声明:私有声明是
提供者和消费者所共同定义的声明
,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。 - 举例如下:
{
"sub": "1000000001",
"name": "bajie",
"admin": true
}
- 然后将其进行base64加密,得到Jwt的第二部分:
eyJzdWIiOiIxMjk1NjJfRkVFREJBQ0siLCJleHAiOjE1OTQ5NzQxNjIsImJlbG9uZ1RvSWQiOjEsImlhdCI6MTU5MjM4MjE2Mn0
签证(signature)
- 第三部分是一个签证信息,这个签证信息由三部分组成:header(base64加密后)、payload(base64加密后)、secret。
- 这个部分需要base64加密后的header和base64加密后的payload使用
.
连接组成的字符串,然后通过header中声明的加密方式进行加盐secret
组合加密,然后就构成了jwt的第三部分:
wrsn8OiDBAvu8uHhIR01ns2qnP6y-SE0ziEjb8wjxUA
- 最后,三部分用
.
组合就构成了最终的jwt。
应用实践
一般用来作为登录凭证
- 首先用户需要先登录,登录成功之后会在成功处理器
构建一个JWT给到前端
(或是响应头的方式返回或是直接塞进Cookie中)。 - 然后用户每次请求接口的时候都需要
解析验证JWT是否有效
。
实战
- 添加依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
- 构建JWT
LoginDTO user = (LoginDTO)authentication.getPrincipal();
user.setUserType("BA");
Date date = new Date(System.currentTimeMillis() + securityConfig.getTokenExpireTimeInSecond() * 1000);
// 使用head中声明的HMAC256加密方式,再加Salt组合加密
Algorithm algorithm = Algorithm.HMAC256(securityConfig.getTokenEncryptSalt());
String token = JWT.create()
.withSubject(user.getId() + "_" + "BA") // 公有声明
.withClaim(LoginDTO.BELONG_TO_ID, user.getBelongToId()) // 私有声明
.withExpiresAt(date) // 设置过期时间
.withIssuedAt(new Date()) // 构建时间
.sign(algorithm); // 签证
// 将JWT以响应头的方式返回前端
response.setHeader(securityConfig.getTokenName(), token);
- 解析JWT
DecodedJWT jwt = ((JwtAuthenticationToken)authentication).getToken();
String subject = jwt.getSubject();
String userId = subject.split("_")[0];
String userType = subject.split("_")[1];
try {
Algorithm algorithm = Algorithm.HMAC256(securityConfig.getTokenEncryptSalt());
JWTVerifier verifier = JWT.require(algorithm)
.withSubject(subject)
.build();
verifier.verify(jwt.getToken());
} catch (TokenExpiredException e) {
throw AuthenticationException.JWT_EXPIRED;
} catch (Exception e){
throw new BadCredentialsException("JWT token verify fail", e);
}