SpringBoot3+Jwt+Redis的Token有效性检查以及自动续期的实现

环境

springboot 3.1.5
maven:3.6.1
java:17

思路

  1. 续期在后端完成,只给前端传递一个token,不采用两个token的方式,从而不增加前端工作量
  2. 默认情况下解析Jwt token 时过期的token将抛出过期错误,因为需要自动续期,因此应忽略该错误。见代码片段1
  3. token等信息保存在Redis中,并设置过期时间为token的过期时间。过期后保存信息自动删除 。修改此处过期时间实现延期
  4. 记录每次前端请求的时间至Redis中。
  5. 验证token有效性时,若距离最近一次请求的时差未超过定义的过期时间增量,则尝试进行续期操作。见代码片段2。代码注解很详细,就不在重复

代码片段1

  /**
     * 获取Claims
     *
     * @param bearerToken 带前缀的token
     * @return Claims
     */
    public static Claims getClaims(String bearerToken) {
        if (StringUtils.isBlank(bearerToken)) {
            return null;
        }
        Claims claims = null;
        try {
            String token = formBearerTokenToToken(bearerToken);
            if (StringUtils.isNotBlank(token)) {
                claims = Jwts.parser().setSigningKey(JwtParams.getSecret()).parseClaimsJws(token).getBody();
            }
        } catch (ExpiredJwtException e) {
            //过期依然返回claims
            claims = e.getClaims();
//            log.info("获取Claims时已过期, Error:{}; token:{}", e.getLocalizedMessage(), bearerToken);

        } catch (Exception e) {
            log.info("获取Claims时出错,token:{}; Error:{}", bearerToken, e.getLocalizedMessage());
        }
        return claims;
    }

代码片段2

 /**
     * @description 判断request的token是否有效,若过期尝试自动续期
     * <br>
     * <pre>
     *     判断及操作流程:
     *     1.从request中获取token,其是否格式正确(前缀及签发者)且含有userId。为假,直接返回false
     *     2.判断request中token是否与cache中的token一致。不一致,直接返回false
     *     3.request中的token,是否未过期
     *       3.1 token未过期,直接返回true!!
     *       3.2 token过期,判断最后一次操作与当前时间差是否超过配置的过期时间增量(JwtParams.getExpireTime())
     *         3.2.1 超过配置的过期时间增量,返回false
     *         3.2.2 未超过,尝试自动续期
     *           3.2.2.1 自动续期成功,返回true!!
     *           3.2.2.2 自动续期失败,返回false
     * <pre>
     * @param request    request
     * @return boolean 是否成功
     * @author MuYi
     * @date 2023/12/12 12:57
     */
    public boolean tokenIsValid(HttpServletRequest request) {
        if (request == null) return true;
        String bearerToken = JwtTokenUtils.getToken(request);
        // 验证request是否格式及签发者正确、是否含userId
        if (!JwtTokenUtils.tokenIsCorrect(bearerToken)) return false;
        Long userId = JwtTokenUtils.getUserId(bearerToken);
        if (userId == null) return false;
        String requestInfo = request.getLocalAddr() + ":" + request.getLocalPort() + request.getRequestURI();
        // 验证request与cache中的token是否一致
        String cacheToken = JwtCacheUtils.getTokenFromCache(userId);
        //cache中无token。表示用户未登录或已注销或系统重启
        //依据前提:
        // 1 token的核验以cache中的token为准
        // 2 当用户登录时cache中保存其token
        // 3 当用户注销时、系统关闭时删除其cache中的token
        if (cacheToken == null) {
            forceLogout(userId, "使用已注销令牌", requestInfo);
            return false;
        }
        if (!bearerToken.equals(cacheToken)) {
            forceLogout(userId, "令牌不一致", requestInfo);
            return false;
        }
        // 验证request中的token是否未过期
        if (!JwtTokenUtils.tokenIsExpired(bearerToken)) return true;
        //验证距离最后一次操作时间是否超过配置的过期时间增量
        Long lastOperateTime = JwtCacheUtils.getLastOperateTime(userId);
        //计算距离最后一次操作时间差,增加TOKEN_ADVANCE_CHECK_TIME时间量,以免出现误差
        long diff = System.currentTimeMillis()+TOKEN_ADVANCE_CHECK_TIME - lastOperateTime;
        if (diff > JwtParams.getExpireTime()) {
            forceLogout(userId, "令牌过期", requestInfo);
            return false;
        }
        //尝试自动续期
        Boolean renewal = JwtCacheUtils.renewalTokenExpireTime(userId);

        if (renewal) {
            return true;
        } else {
            forceLogout(userId, "尝试自动续期失败", requestInfo);
            return false;
        }
    }

    protected void forceLogout(Long userId, String msg, String requestInfo) {
        try {
            log.info("{},将强制用户(ID={})退出。(request:{})", msg, userId, requestInfo);
            logoutProcess(userId);
//                request.setAttribute("renewal",false);
        } catch (Exception e) {
            log.info("{},将强制用户(ID={})退出。(request:{})。发生错误{}", msg, userId, requestInfo, e.getLocalizedMessage());
        }
    }

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Spring Boot是一个开源的Java框架,用于构建独立的、可执行的、生产级的Spring应用程序。它极大地简化了Spring应用程序的搭建和部署过程,提供了一整套开箱即用的特性和插件,极大地提高了开发效率。 Shiro是一个强大且灵活的开源Java安全框架,提供了身份验证、授权、加密和会话管理等功能,用于保护应用程序的安全。它采用插件化的设计,支持与Spring等常用框架的无缝集成,使开发者能够轻松地在应用程序添加安全功能。 JWT(JSON Web Token)是一种用于在客户端和服务端之间传输安全信息的开放标准。它使用JSON格式对信息进行包装,并使用数字签名进行验证,确保信息的完整性和安全性。JWT具有无状态性、可扩展性和灵活性的特点,适用于多种应用场景,例如身份验证和授权。 Redis是一个开源的、高性能的、支持多种数据结构的内存数据库,同时也可以持久化到磁盘。它主要用于缓存、消息队列、会话管理等场景,为应用程序提供高速、可靠的数据访问服务。Redis支持丰富的数据类型,并提供了强大的操作命令,使开发者能够灵活地处理各种数据需求。 综上所述,Spring Boot结合Shiro、JWTRedis可以构建一个安全、高性能的Java应用程序。Shiro提供了强大的安全功能,包括身份验证和授权,保护应用程序的安全;JWT用于安全传输信息,确保信息的完整性和安全性;Redis作为缓存和持久化数据库,提供了高速、可靠的数据访问服务。通过使用这些技术,开发者能够快速、高效地构建出符合安全和性能需求的应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

muyi517

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值