目前存在问题
我们之前可以使用退出登录接口直接退出,用户Session中的验证信息也会被销毁,但是现在是无状态的,用户来管理Token令牌,服务端只认Token是否合法,那这个时候该怎么让用户正确退出登录呢?
首先我们从最简单的方案开始,我们可以直接让客户端删除自己的JWT令牌,这样不就相当于退出登录了吗.
这样虽然是最简单粗暴的,但是存在一个问题**,用户可以自行保存这个Token拿来使用**。虽然客户端已经删除掉了,但是这个令牌仍然是可用的,如果用户私自保存过,那么依然可以正常使用这个令牌,这显然是有问题的。
目前有两种比较好的方案:
- 黑名单方案:所有黑名单中的JWT将不可使用。
- 白名单方案:不在白名单中的JWT将不可使用。
黑名单机制实现
这里我们以黑名单机制实现,这里使用redis实现黑名单机制。
导入redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
编写令牌失效方法
在JwtUtils内,编写令牌失效方法
这里先校验令牌,获取令牌,在将他传入加入黑名单的方法
我们创建jwt令牌时,是设置了uuid的,这样确保uuid的唯一性;这里先验证token是不是存在黑名单中,不在就设置过期时间,并将其存入redis中,我们还要设置常量,来区分redis存的内容,因为之后还要存redis
到此就实现了黑名单
/**
* 让指定Jwt令牌失效
* @param headerToken 请求头中携带的令牌
* @return 是否操作成功
*/
public boolean invalidate(String headerToken){
// 校验headerToken
String token = this.convertToken(headerToken);
if (token == null)
return false;
Algorithm algorithm = Algorithm.HMAC256(key);
JWTVerifier jwtVerifier = JWT.require(algorithm).build();
try {
DecodedJWT verify = jwtVerifier.verify(token);
// 加入黑名单
return deleteToken(verify.getId(),verify.getExpiresAt());
} catch (JWTVerificationException e) {
return false;
}
}
/**
* 将Token列入Redis黑名单中
* @param uuid 令牌ID
* @param time 过期时间
* @return 是否操作成功
*/
public boolean deleteToken(String uuid, Date time) {
// 验证Token是否被列入Redis黑名单
if (isInvalidToken(uuid)) {
return false;
}
// 验证是否过期
Date now = new Date();
long expire =Math.max(time.getTime()-now.getTime(), 0);
// 将Token列入Redis黑名单中
redisTemplate.opsForValue().set(Const.JWT_BLACK_LIST + uuid,"", expire, TimeUnit.MILLISECONDS);
return true;
}
/**
* 验证Token是否被列入Redis黑名单
* @param uuid 令牌ID
* @return 是否操作成功
*/
private boolean isInvalidToken(String uuid){
return Boolean.TRUE.equals(redisTemplate.hasKey(Const.JWT_BLACK_LIST + uuid));
}
完善解析令牌方法
完善处理器
回到SecurityConfiguration完善登出处理器
我们照常设置编码类型,再获取令牌,最后调用加入黑名单方法
ok~~,请自行测试