JWT 的过期与刷新怎么设计?Refresh Token 机制怎么做?
- 标签:Spring Security、认证授权、后端
🏛 业务架构师
JWT 过期与刷新的标准设计——双令牌机制:
结构:
- Access Token:短时效(15 分钟~2 小时),每次请求携带做认证——泄露的爆炸半径被时效限定;
- Refresh Token:长时效(7~30 天),只用于换新 Access Token(专用的
/auth/refresh端点),不参与业务请求;服务端存储(DB/Redis),因此可撤销、可追踪。
刷新流程:
- Access Token 过期 → 客户端(拦截器)拿 Refresh Token 调 refresh 接口;
- 服务端验证 Refresh Token(签名+查存储是否有效/被撤销)→ 签发新 Access Token(可选:轮换新 Refresh Token,旧的作废——防重放);
- Refresh Token 也过期 → 引导重新登录。
设计要点(生产必看):
- Refresh Token 一次一换(轮换):旧的立即作废;检测到已用的旧 Token 再次出现 → 判定泄露,全族作废强制重登(Token 家族策略);
- 存储带来可控性:用户改密、封号、踢下线 → 删 Refresh Token 即可,最多等 Access Token 自然过期(15 分钟窗口)——这就是"无状态 JWT + 有状态 Refresh"的混合哲学:用小的有状态换大的无状态便利;
- 并发刷新:多端同时刷 → 用锁或宽限期防竞态产生多个家族;
- 客户端体验:无感刷新(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 机制要点(防泄攻防是核心):
- 服务端必须可撤销:refresh 存 Redis/DB(
token_id(jti) → user_id, 过期时间, 设备指纹, 状态)——登出/改密/被盗时可删;这是它和 access 的本质区别(access 不存库); - 旋转+重用检测(Rotation & Reuse Detection):每次刷新换新 refresh、旧 jti 作废;若已作废的旧 refresh 又被使用——大概率泄露攻击,立即吊销该用户全部会话并告警强制重登;
- 绑定通道:HttpOnly+Secure+SameSite Cookie 传 refresh(比 localStorage 抗 XSS);可绑设备指纹/IP 变化二次校验;
- 分级有效期:普通设备 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)。