Skip to content

JWT 的过期与刷新怎么设计?Refresh Token 机制怎么做? ​

  • 标签:Spring Security、认证授权、后端

🏛 业务架构师 ​

JWT 过期与刷新的标准设计——双令牌机制:

结构:

  • Access Token:短时效(15 分钟~2 小时),每次请求携带做认证——泄露的爆炸半径被时效限定;
  • Refresh Token:长时效(7~30 天),只用于换新 Access Token(专用的 /auth/refresh 端点),不参与业务请求;服务端存储(DB/Redis),因此可撤销、可追踪。

刷新流程:

  1. Access Token 过期 → 客户端(拦截器)拿 Refresh Token 调 refresh 接口;
  2. 服务端验证 Refresh Token(签名+查存储是否有效/被撤销)→ 签发新 Access Token(可选:轮换新 Refresh Token,旧的作废——防重放);
  3. Refresh Token 也过期 → 引导重新登录。

设计要点(生产必看):

  1. Refresh Token 一次一换(轮换):旧的立即作废;检测到已用的旧 Token 再次出现 → 判定泄露,全族作废强制重登(Token 家族策略);
  2. 存储带来可控性:用户改密、封号、踢下线 → 删 Refresh Token 即可,最多等 Access Token 自然过期(15 分钟窗口)——这就是"无状态 JWT + 有状态 Refresh"的混合哲学:用小的有状态换大的无状态便利;
  3. 并发刷新:多端同时刷 → 用锁或宽限期防竞态产生多个家族;
  4. 客户端体验:无感刷新(401 自动 refresh 重试)对 App 是标配,注意别把 Refresh Token 写进 localStorage(XSS 风险)——App 用安全存储、Web 用 HttpOnly Cookie。

这套设计在"安全窗口"与"用户体验"之间取平衡,是 2026 年 B 端系统的默认答案。

🏗 系统架构师 ​

过期与刷新的双令牌设计(Access + Refresh)——核心矛盾:Access 要随身携带(放 Header,每请求都传)→ 必须短命(泄露损失小);Refresh 只在认证服务用一次一换 → 可以长命但要安全保管。短 Access 换风险控制,长 Refresh 换用户体验。

典型参数:Access 15min~2h;Refresh 7~30 天(配合轮换与滑到期)。

Refresh 机制的标准设计:

  • 签发:登录成功同时发两枚(Access 给前端内存/Authorization Header;Refresh 走 HttpOnly + Secure + SameSite Cookie——防 XSS 窃取,这是与 localStorage 方案的关键安全差异);
  • 刷新:Access 过期 → 前端携 Refresh 调 /token/refresh → 认证服务验 Refresh(签名 + 是否在吊销名单)→ 签发新 Access(可同时轮换 Refresh:旧 Refresh 作废发新的一一一次一换,泄露检测:已用过的 Refresh 再次出现 = 被盗,全端强制下线);
  • 并发刷新竞态:多标签页同时刷新——前端单飞(single-flight)锁或后端宽限期(旧 Refresh 短暂有效);
  • 多端管理:Refresh 按"会话/设备"维度存储(服务端表:tokenId、userId、设备指纹、IP、过期、状态)——支持"踢出该设备"(下线即把该条标记吊销)。

架构权衡:纯短 Access 无 Refresh(频繁重登录,体验差);纯长 Access(泄露窗口大);双令牌 + 轮换 + 服务端 Refresh 存储(可吊销)是生产标准答案——注意 Refresh 有状态(存储/查询)后,"JWT 无状态"的优势实际保留在 Access 上:业务服务验 Access 依然零存储。

SSO/移动端变体:OIDC 标准化了这套(refresh_token grant),自研建议对齐 OIDC 语义别发明轮子。

💻 全栈开发专家 ​

设计原则:Access Token 短命(15~60 分钟,不可吊销靠时间兜底)+ Refresh Token 长命(7~30 天,可吊销可管控)——用「可撤销的长凭证换不可撤销的短凭证」,平衡安全与体验。

完整流程(我的标准实现):

1. 登录:验账密 → 发 access_token(30min) + refresh_token(7d, 存库)
2. access 过期 → 401 → 前端调 /token/refresh(带 refresh_token)
3. 服务端校验 refresh(签名+有效期+是否在库中未撤销)
   → 签发新 access(可旋转 refresh:发新的并废弃旧的)
   → 前端无缝续期

Refresh Token 机制要点(防泄攻防是核心):

  1. 服务端必须可撤销:refresh 存 Redis/DB(token_id(jti) → user_id, 过期时间, 设备指纹, 状态)——登出/改密/被盗时可删;这是它和 access 的本质区别(access 不存库);
  2. 旋转+重用检测(Rotation & Reuse Detection):每次刷新换新 refresh、旧 jti 作废;若已作废的旧 refresh 又被使用——大概率泄露攻击,立即吊销该用户全部会话并告警强制重登;
  3. 绑定通道:HttpOnly+Secure+SameSite Cookie 传 refresh(比 localStorage 抗 XSS);可绑设备指纹/IP 变化二次校验;
  4. 分级有效期:普通设备 7 天、受信设备 30 天、敏感操作后强制缩短。
java
@PostMapping("/token/refresh")
public TokenResponse refresh(@CookieValue("rt") String rt) {
    var jti = parse(rt).jti();
    var stored = redis.get("rt:" + jti)
        .orElseThrow(() -> new ReuseDetectedException(jti));  // 旧token重用→全端下线
    ... // 校验→旋转:del 旧 jti,set 新 jti,发新对
}

坑:并发刷新(多 tab 同时 401 各自刷新)——前端加刷新互斥锁/单飞;别把 refresh 放 URL(日志泄露);注销时两端都清(前端 cookie + 服务端 jti)。