基于 Redis 的 JWT令牌失效处理方案(实现步骤)
作者:Mr.VK
当用户登录状态到登出状态时,对应的JWT的令牌需要设置为失效状态,这时可以使用基于Redis 的黑名单方案来实现JWT令牌失效,本文给大家分享基于 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); } }
到此这篇关于基于 Redis 的 JWT令牌失效方案的文章就介绍到这了,更多相关Redis JWT令牌失效内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!