JWT 鉴权中的单 Token 与双 Token方案
JWT 鉴权中的单 Token 与双 Token 方案对比:控制力、安全性与实用性解析
在使用 JWT 进行身份验证与续签时,常见有两种方案:
- 方案一:双 Token 模式(Access Token + Refresh Token)
- 方案二:单 Token 模式(JWT 中嵌入续签时间)
这两种方案都在社区中被使用,但在安全性、控制力和设计合理性上存在关键差异。本文将系统性地比较这两种方案,并解释为什么“双 Token 模式”在实际项目中被广泛采用。
✅ 背景介绍
方案一:双 Token 模式
access_token
:短期有效,用于访问受保护资源,通常 15-30 分钟过期;refresh_token
:长期有效,用于换发新的 access_token,通常存放在 HttpOnly Cookie,1 小时~7 天过期。
续签过程必须通过服务器验证 refresh_token
,服务器主动决定是否签发新 token。
方案二:单 Token 模式
- 仅使用一个 JWT;
- JWT 中添加一个自定义字段,如
renew_after
; - 当 token 尚未过期但已超过
renew_after
时间,客户端自动请求续签; - 服务器根据 token 的状态和其他逻辑决定是否签发新 token。
🎯 本质区别:控制点与安全边界
✅ 方案一的设计优势:清晰分权,服务端可控
- 续签逻辑必须走服务器,服务器拥有完全的决策权;
refresh_token
可以存储上下文信息,如 IP、User-Agent、设备 ID;- 可以配合数据库或 Redis 实现状态管理、吊销机制;
- 清晰分离两个 token 的职责 —— 一个访问资源,一个控制续签。
⚠️ 方案二的问题:逻辑耦合,服务端被动判断
- 一个 token 同时承担访问和续签判断,职责不清晰;
- 续签行为依赖客户端逻辑,服务端只能被动“拒绝续签”;
- 若要实现撤销,必须维护黑名单系统,违背 JWT 无状态设计;
- 安全边界模糊,难以处理设备隔离、上下文绑定等需求。
🛡️ 安全性比较
比较维度 | 双 Token 模式 | 单 Token 模式 |
---|---|---|
token 泄露后影响范围 | access_token 泄露影响小(短期) | 唯一 token 泄露即拥有访问 + 续签能力 |
refresh_token 是否 HttpOnly 存储 | ✅ 是 | ❌ 无(或不存在 refresh_token) |
是否支持服务端立即吊销续签能力 | ✅ 可以(拒绝 refresh_token) | ⚠️ 只能靠黑名单绕过 JWT 无状态限制 |
是否能绑定设备/上下文信息 | ✅ 可绑定并检查 | ⚠️ 难以实现,需自行扩展校验逻辑 |
🏗️ 架构与扩展性对比
比较维度 | 双 Token 模式 | 单 Token 模式 |
---|---|---|
OAuth2 等标准支持 | ✅ 完全兼容 | ❌ 非标准自定义逻辑 |
与移动端或 SPA 接入兼容性 | ✅ 好(HttpOnly Cookie 配合可选) | ⚠️ 移动端需扩展 token 管理 |
控制粒度 | ✅ 高(每个 token 可单独管理) | ⚠️ 低(token 自包含,服务端只能被动处理) |
设计职责清晰度 | ✅ 分离访问与续签 | ❌ 访问与续签混杂在一个 token 内 |
token 续签逻辑可追踪性 | ✅ 强(refresh_token 可追踪) | ⚠️ 弱(单 token 无状态不可追踪) |
👨💻 那单 Token 模式真的不行吗?
不是。
单 token 模式 理论上可以实现大多数控制逻辑,包括:
- 黑名单;
- Token 数据库存储与状态管理;
- 上下文校验(IP、设备等);
- 主动拒绝续签请求。
但这些实现通常是对 JWT 无状态设计的“补丁”:
- 自定义字段;
- 额外存储;
- 黑名单机制;
- 逻辑嵌套在 JWT 校验中。
因此:
如果你愿意承担额外的实现和维护成本,单 token 模式也是可行的,但它违背了 JWT 的无状态特性,也难以在大型系统中长期维护。
🔚 总结
项目 | 双 Token 模式 | 单 Token 模式 |
---|---|---|
控制力 | ⭐⭐⭐⭐⭐ | ⭐⭐ |
实现复杂度 | ⭐⭐⭐ | ⭐ |
安全性 | ⭐⭐⭐⭐ | ⭐⭐ |
标准兼容性 | ⭐⭐⭐⭐⭐ | ⭐⭐ |
实际应用广度 | 广泛应用(主流) | 偶有使用(非主流) |
✅ 推荐:生产环境优先采用双 token 模式,将访问控制与续签控制职责分离,提升安全性与可控性。
📎 附录:什么是 HttpOnly Cookie?
- HttpOnly Cookie 是浏览器中只能被服务器访问的 cookie,JavaScript 无法读取;
- 常用于存储 refresh_token,有效防止 XSS 攻击;
- 设置方式:
- Java
1
2
3
4
5
6
7
8public void setHttpOnlyCookie(HttpServletResponse response, String name, String value) {
Cookie cookie = new Cookie(name, value);
cookie.setHttpOnly(true); // 防止 JavaScript 访问
cookie.setSecure(true); // 仅 HTTPS 下传输(生产环境推荐开启)
cookie.setPath("/"); // 有效路径
cookie.setMaxAge(60 * 60 * 24); // 有效期(秒)
response.addCookie(cookie);
} - Node/Express:
res.cookie('refresh_token', token, { httpOnly: true, secure: true })
- Flask:
resp.set_cookie('refresh_token', token, httponly=True)
- Java
如果你正在设计一个带身份认证的前后端分离系统,建议选择双 token 模式,并结合 HttpOnly Cookie 存储 refresh_token,以获得更高的安全性与控制能力。
最后附上,个人博客网站:Southblock’Blog,内容更多,更新,欢迎参观。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Southblock'Blog!