基于 Redis 的 JWT令牌失效方案
应用场景
当用户登录状态到登出状态时,对应的JWT的令牌需要设置为失效状态,这时可以使用基于 Redis 的黑名单方案来实现JWT令牌失效。
基于 Redis 的黑名单方案
当用户需要登出系统时,将用户携带的Token进行解析,解码出JWT令牌,取出对应的 UUID 和 过期时间 ,用过期的时间减去当前的时间,计算出这个Key的过期时间,再以这两个字段拼接作为 Key 并设置好过期时间存储到 Redis 中,如果有黑客拿窃取出来的JWT令牌进行登录,只要判断这个JWT令牌是否在黑名单就可以。
实现步骤
1.获得携带的Token解析并取出JWT令牌的代码
这段代码实现了对指定 JWT 的验证和使令牌失效的操作。
- 首先,通过调用 convertToken(headerToken) 方法将传入的头部令牌 headerToken 转换成实际的 JWT 字符串 token。
- 然后,使用 HMAC256 算法和预设的密钥 key 创建一个算法实例 algorithm。
- 接下来,使用算法实例 algorithm 构建一个 JWT 验证器 jwtVerifier。这个验证器将用于验证 JWT 的有效性。
- 在 try-catch 块中,首先通过调用 jwtVerifier.verify(token) 方法对 JWT 进行验证。如果验证成功,则返回一个 DecodedJWT 对象 verify,其中包含了 JWT 的解码信息,如令牌的唯一标识符(ID)和过期时间等。
- 接着,调用 deleteToken(verify.getId(), verify.getExpiresAt()) 方法来删除指定令牌,并将其加入到黑名单中进行失效处理。这里使用了 verify 对象中的 ID 和过期时间作为参数。
- 最后,如果在验证 JWT 过程中发生了 JWTVerificationException 异常,即 JWT 验证失败,则捕获该异常,并返回 false 表示令牌失效操作失败。
/**
* 让指定Jwt令牌失效
* @param headerToken 请求头中携带的令牌
* @return 是否操作成功
*/
public boolean invalidateJwt(String headerToken){
String token = this.convertToken(headerToken);
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;
}
}
2.检查指定 UUID 的令牌是否为无效的(已加入黑名单)
这段代码用于检查指定 UUID 的令牌是否为无效的(已加入黑名单),通过判断 Redis 数据库中是否存在相应的键来决定令牌的有效性。如果键存在,则表示令牌已失效;如果键不存在,则表示令牌仍然有效。
/**
* 验证Token是否被列入Redis黑名单
* @param uuid 令牌ID
* @return 是否操作成功
*/
private boolean isInvalidToken(String uuid){
return Boolean.TRUE.equals(template.hasKey(Const.JWT_BLACK_LIST + uuid));
}
3.将Token列入Redis黑名单中
这段代码实现了对指定令牌的删除和加入黑名单的操作,用于管理令牌的有效性和安全性。
- 首先,通过调用 isInvalidToken(uuid) 方法来检查指定的 UUID 是否为无效的令牌。如果 isInvalidToken 方法返回 true,则说明该令牌无效,此时直接返回 false,不执行后续操作。
- 获取当前时间 now,然后计算令牌的过期时间与当前时间的差值,并取最大值作为令牌的失效时间 expire。这里使用了 Math.max 方法来确保失效时间不会小于 0。
- 最后,通过 Redis 的 template 对象调用 opsForValue().set() 方法,将指定 UUID 的令牌加入到名为 Const.JWT_BLACK_LIST + uuid 的键中,并设置过期时间为 expire 毫秒。这样就将该令牌加入到了黑名单中,使其在一定时间后失效。
- 最终,方法返回 true 表示成功删除令牌并将其加入黑名单。
/**
* 将Token列入Redis黑名单中
* @param uuid 令牌ID
* @param time 过期时间
* @return 是否操作成功
*/
private boolean deleteToken(String uuid, Date time){
if(this.isInvalidToken(uuid))
return false;
Date now = new Date();
long expire = Math.max(time.getTime() - now.getTime(), 0);
template.opsForValue().set(Const.JWT_BLACK_LIST + uuid, "", expire, TimeUnit.MILLISECONDS);
return true;
}
public final class Const {
//JWT令牌
public final static String JWT_BLACK_LIST = "jwt:blacklist:";
public final static String JWT_FREQUENCY = "jwt:frequency:";
}
对应完整的代码如下:
@Component
public class JwtUtils {
@Autowired
private StringRedisTemplate template;
@Value("${spring.security.jwt.key}")
String key;
@Value("${spring.security.jwt.expire}")
int expire;
/**
* 让指定Jwt令牌失效
* @param headerToken 请求头中携带的令牌
* @return 是否操作成功
*/
public boolean invalidateJwt(String headerToken){
String token = this.convertToken(headerToken);
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 是否操作成功
*/
private boolean deleteToken(String uuid, Date time){
if(this.isInvalidToken(uuid))
return false;
Date now = new Date();
long expire = Math.max(time.getTime() - now.getTime(), 0);
template.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(template.hasKey(Const.JWT_BLACK_LIST + uuid));
}
public DecodedJWT resolveJwt(String headerToken) {
String token = this.convertToken(headerToken);
if (token == null) {
return null;
}
Algorithm algorithm = Algorithm.HMAC256(key);
JWTVerifier jwtVerifier = JWT.require(algorithm).build();
try {
DecodedJWT verify = jwtVerifier.verify(token);
if(this.isInvalidToken(verify.getId())) return null;
Date expireAt = verify.getExpiresAt();
return new Date().after(expireAt) ? null : verify;
} catch (JWTVerificationException e) {
return null;
}
}
public UserDetails toUser(DecodedJWT jwt) {
Map<String, Claim> claims = jwt.getClaims();
return User.withUsername(claims.get("name").asString())
.password("********")
.authorities(claims.get("authorities").asArray(String.class))
.build();
}
public Integer toId(DecodedJWT jwt) {
Map<String, Claim> claims = jwt.getClaims();
return claims.get("id").asInt();
}
public String createJwt(UserDetails details, int id, String username) {
Algorithm algorithm = Algorithm.HMAC256(key);
Date expire = this.expireTime();
return JWT.create()
.withJWTId(UUID.randomUUID().toString())
.withClaim("id", id)
.withClaim("name", username)
.withClaim("authorities", details.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList())
.withExpiresAt(expire)
.withIssuedAt(new Date())
.sign(algorithm);
}
public Date expireTime() {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.HOUR, expire * 24);
return calendar.getTime();
}
private String convertToken(String headerToken) {
if(headerToken == null || !headerToken.startsWith("Bearer ")) {
return null;
}
return headerToken.substring(7);
}
}
原文地址:https://blog.csdn.net/Mr_VK/article/details/136456365
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!