1 JWT
1.1 组成
JWT由JWT头,载荷和前面,Signature 部分是对前两部分的签名,防止数据被篡改。其中JWT头和载荷为Base64URL编码,Signature通过密钥根据算法生成。
1.2 使用
- 引入依赖
1
2
3
4
5
|
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
|
- 加密与解密
1
2
3
4
5
6
7
8
9
10
|
// 加密
String token = JWT.create()
.withClaim(“user”, 用户数据)
.withExpiresAt(new Date(System.currentTimeMillis() + 1000*60*60))
.sign(Algorithm.HMAC256(秘钥));
// 解密
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(“密钥”)).build();
DecodedJWT decodedJWT = jwtVerifier.verify(令牌字符串);//抛异常解析失败
//得到所有载荷
Map<String, Claim> claims = decodedJWT.getClaims();
|
2 拦截器
对于其他接口,在未登录之前不能访问该接口,因此添加拦截器。
- 拦截器声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
@Component
public class JwtTokenAdminInterceptor implements HandlerInterceptor {
@Autowired
private JwtProperties jwtProperties;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断当前拦截到的是Controller的方法还是其他资源
if (!(handler instanceof HandlerMethod)) {
//当前拦截到的不是动态方法,直接放行
return true;
}
//1、从请求头中获取令牌
String token = request.getHeader(jwtProperties.getAdminTokenName());
//2、校验令牌
try {
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
BaseContext.setCurrentId(empId);
//3、通过,放行
return true;
} catch (Exception ex) {
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
}
}
|
- 拦截器注册
1
2
3
4
5
6
7
8
9
10
11
|
@Configuration
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Autowired
private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtTokenAdminInterceptor)
.addPathPatterns("/admin/**")
.excludePathPatterns("/admin/employee/login");
}
}
|
3 ThreadLocal
在用户登录后,界面需要展示信息,因此要调用其他接口,此时需要保存用户ID提升代码复用性。
1
2
3
|
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
BaseContext.setCurrentId(empId);
|
后续接口需要使用empId
从局部变量获取即可。
在请求结束后,清除共享变量。
1
2
3
4
|
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
BaseContext.removeCurrentId();
}
|
4 令牌失效
在用户更新密码等操作后,要将旧令牌失效。
使用redis
技术,在用户登录成功后令牌保存到redis
,修改密码后清除redis
。每次校验jwt的同时对比redis
是否存在该令牌。
- 配置redis起步依赖
1
2
3
4
|
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
|
- 属性配置
1
2
3
4
5
|
spring:
data:
redis:
host: localhost
port: 6379
|
- 登录成功后存token到redis中。
1
2
|
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
operations.set(token, token, 1, TimeUnit.HOURS); //token同时作为键值
|
- 拦截器中获取相同的token
1
2
3
|
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
String redistoken = operations.get(token); //token同时作为键值
// 若查不到该token,删除
|
- 修改密码后删除token
1
2
|
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
operations.getOperations().delete(token);
|