基于Redis实现JWT Token无感续期:Spring Boot实战方案
1. 项目概述与核心价值
在构建现代Web应用,特别是前后端分离架构时,身份认证与授权是绕不开的核心环节。JWT(JSON Web Token)因其无状态、自包含的特性,成为许多开发者的首选方案。然而,无状态这把“双刃剑”也带来了一个经典难题:如何优雅地处理Token的有效期?设置太短,用户频繁被迫重新登录,体验极差;设置太长,又带来严重的安全风险。我最近在重构一个用户量级不小的后台管理系统时,就深度实践了基于Redis的Token在线续期方案,它完美地在安全与体验之间找到了平衡点。这个方案的核心思想,不是简单粗暴地延长JWT本身的过期时间,而是引入一个“活跃会话”的概念,通过Redis这个高性能的内存数据库来记录和管理用户的活跃状态,从而实现无感续期。简单来说,只要用户在持续操作,他的会话就能一直保持有效,一旦闲置超过预设时间,会话才会真正过期。下面,我就把这个从设计思路到代码落地的完整过程,以及踩过的坑和优化心得,毫无保留地分享出来。
2. 整体架构设计与核心思路拆解
2.1 为何选择“Redis + JWT”的组合?
首先,我们需要明确为什么不能单靠JWT来实现续期。JWT一旦签发,其载荷(Payload)中的过期时间(exp)就固定了,服务端无法单方面修改一个已签发Token的内容。强行续期意味着要重新签发一个新Token,这涉及到如何安全地将新Token下发给客户端,以及如何处理旧Token的失效问题,流程会变得复杂。
而Redis的引入,正是为了解决这个“状态管理”的问题。我们将JWT的“无状态认证”与Redis的“有状态会话管理”相结合,形成一种混合模式:
- JWT负责短期认证:Token本身仍携带一个相对较短的有效期(例如30分钟),用于快速验证请求的合法性和携带基础用户信息。
- Redis负责长期会话:在Redis中存储一个与用户关联的会话记录,其有效期远长于JWT(例如7天)。这个记录标识了用户的“活跃会话窗口”。
当用户携带一个未过期的JWT发起请求时,系统不仅校验JWT,还会去Redis检查对应的会话记录是否存在且有效。如果存在,则视为活跃用户,并触发续期逻辑——重置Redis中该会话记录的过期时间。这样,只要用户在持续活动,他的会话就能通过Redis不断“续命”。
2.2 核心流程与数据模型设计
整个流程可以概括为“登录存、请求验、活跃续、登出删”。
数据模型设计:
在Redis中,我们需要一个结构来存储会话。通常使用String或Hash类型。
- Key的设计:
session:${userId}或token:${jti}。使用userId更直观,便于按用户管理所有会话;使用JWT的jti(JWT ID)则更精准,一个Token对应一个会话。我推荐使用session:${userId},因为更符合“用户会话”的业务概念。 - Value的设计:可以存储简单的标志(如
"ACTIVE"),也可以存储一些额外信息,如登录时间、客户端信息等。为了后续可能的扩展,我选择存储一个JSON字符串。 - 过期时间(TTL):这就是我们为会话设置的“最大不活跃持续时间”,例如
604800秒(7天)。
核心流程步骤:
- 登录成功:生成JWT(设30分钟过期),同时,以
userId为键,将会话信息存入Redis,并设置TTL为7天。 - 携带Token请求:拦截请求,从
Authorization头解析JWT。- 校验JWT签名和基本有效期(30分钟内)。
- 从JWT中提取
userId,构造Redis Key去查询会话是否存在。 - 若会话不存在,返回401,提示登录已过期。
- 若会话存在,执行续期:重置该Redis Key的TTL为7天(从头计算)。
- Token过期但会话活跃:这是关键场景。当JWT的30分钟过期后,但Redis中的会话还在7天有效期内。
- 方案一(推荐):在JWT校验逻辑中,对于已过期的Token,如果其过期时间在一个可接受的“宽限期”内(如5分钟),并且Redis会话仍有效,则允许请求通过,并同时签发一个新的JWT返回给客户端(通常通过响应头,如
X-New-Token)。 - 方案二:前端在Token即将过期时(通过定时器或请求拦截),主动调用一个
/refresh接口,该接口校验Redis会话后签发新Token。
- 方案一(推荐):在JWT校验逻辑中,对于已过期的Token,如果其过期时间在一个可接受的“宽限期”内(如5分钟),并且Redis会话仍有效,则允许请求通过,并同时签发一个新的JWT返回给客户端(通常通过响应头,如
- 登出/修改密码:直接删除对应用户在Redis中的会话记录。这样,即使旧的JWT尚未过期,也会因会话不存在而被拒绝。
注意:方案一(滑动过期+静默刷新)对前端更友好,实现了真正的无感续期。但需要前端配合处理可能的新Token。方案二需要前端主动管理,逻辑稍显复杂。
3. 核心组件实现与代码详解
接下来,我们进入实战环节,基于Spring Boot一步步实现上述逻辑。我会假设你已经有一个基础的Spring Boot项目,并整合了Spring Security、JWT和Redis。
3.1 环境准备与依赖引入
首先,在pom.xml中添加必要的依赖:
在application.yml中配置Redis连接:
3.2 JWT工具类与Redis会话服务封装
我们需要一个工具类来生成和解析JWT,以及一个服务来管理Redis中的会话。
JWT工具类 (JwtUtil):