基于 Redis 的 JWT令牌失效处理方案(实现步骤)

 更新时间:2024年03月05日 10:35:36   作者: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令牌失效内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Redis高性能的原因及说明

    Redis高性能的原因及说明

    这篇文章主要介绍了Redis高性能的原因及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-10-10
  • Redis中Scan命令的基本使用教程

    Redis中Scan命令的基本使用教程

    这篇文章主要给大家介绍了关于Redis中Scan命令的基本使用教程,文中通过示例代码介绍的非常详细,对大家学习或者使用Redis具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-06-06
  • redis在php中常用的语法【推荐】

    redis在php中常用的语法【推荐】

    string是redis最基本的类型,而且string类型是二进制安全的。这篇文章主要介绍了redis在php中常用的语法,需要的朋友可以参考下
    2018-08-08
  • Redis中的动态字符串学习教程

    Redis中的动态字符串学习教程

    这篇文章主要介绍了Redis中的动态字符串学习教程,以sds模块的使用为主进行讲解,需要的朋友可以参考下
    2015-08-08
  • 在CentOS 7环境下安装Redis数据库详解

    在CentOS 7环境下安装Redis数据库详解

    Redis是一个开源的、基于BSD许可证的,基于内存的、键值存储NoSQL数据本篇文章主要介绍了在CentOS 7环境下安装Redis数据库详解,有兴趣的可以了解一下。
    2016-11-11
  • 基于Redis实现抽奖功能及问题小结

    基于Redis实现抽奖功能及问题小结

    这篇文章主要介绍了基于Redis实现抽奖功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-08-08
  • 使用SpringBoot + Redis 实现接口限流的方式

    使用SpringBoot + Redis 实现接口限流的方式

    这篇文章主要介绍了SpringBoot + Redis 实现接口限流,Redis 除了做缓存,还能干很多很多事情:分布式锁、限流、处理请求接口幂等,文中给大家提到了限流注解的创建方式,需要的朋友可以参考下
    2022-05-05
  • Redis中不同持久化方式的差异对比

    Redis中不同持久化方式的差异对比

    大家应该都知道,Redis持久化方式主要有两种:RDB(Redis DataBase)和AOF(Append-only file),但是他们各自存储了什么内容?有什么差异呢?今天我来给大家做个小试验,需要的朋友可以参考下
    2024-03-03
  • redis集群实现清理前缀相同的key

    redis集群实现清理前缀相同的key

    这篇文章主要介绍了redis集群实现清理前缀相同的key,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • 一篇文章带你彻底搞懂Redis 事务

    一篇文章带你彻底搞懂Redis 事务

    这篇文章主要介绍了一篇文章带你彻底搞懂Redis 事务的相关资料,需要的朋友可以参考下
    2022-10-10

最新评论