双Token机制在实际项目中的应用与实践
在现代Web应用与移动应用中,用户认证是保障系统安全的核心环节。传统的基于Session的单Token机制存在服务端存储压力大、跨域支持困难、移动端适配复杂等痛点。本文结合理论分析与实际项目经验,系统阐述 Access Token + Refresh Token 双令牌机制的设计思想、安全模型与落地要点,帮助读者不仅“会用”,更能“懂用”。
一、什么是双Token机制?
双Token机制是基于JWT(JSON Web Token)的一种认证方案,通过两个不同生命周期的令牌协同工作,兼顾安全性与用户体验。
Token类型 作用 有效期 推荐存储位置
Access Token 访问受保护资源的凭证 短(1-2小时) 客户端内存(易失)
Refresh Token 获取新的Access Token 长(7-30天) 安全持久化存储
核心交互流程
客户端 服务端
│ 1. 登录请求 │
├─────────────────────→│
│ 2. 返回双Token │
│←─────────────────────┤
│ 3. 携带Access Token │
│ 请求API │
├─────────────────────→│
│ 4. 返回资源 │
│←─────────────────────┤
│ 5. Access Token过期 │
│ 携带Refresh Token │
│ 请求刷新 │
├─────────────────────→│
│ 6. 返回新Token对 │
│←─────────────────────┤
二、双Token机制的理论基础
2.1 为什么不能只用单Token?
安全与体验的矛盾:若Token有效期短,用户需频繁登录,体验差;若有效期长,一旦泄露,攻击者拥有极长的攻击窗口。
无状态带来的失控:JWT签发后无法主动撤销(除非引入黑名单),用户修改密码后,旧Token依然有效。
无法精细控制权限:不能单独吊销某个设备或某个会话。
2.2 双Token如何解决?
分层安全策略:Access Token作为短期钥匙,即使泄露也很快失效;Refresh Token作为长期凭证,但仅在刷新接口使用,暴露面小。
有状态 + 无状态混合:Access Token保持无状态,便于水平扩展;Refresh Token与Redis会话绑定,实现主动管理和吊销。
用户无感续期:前端拦截401自动刷新,保证用户持续在线。
2.3 与OAuth 2.0的异同
双Token机制借鉴了OAuth 2.0的授权码模式思想,但更轻量:
特性 OAuth 2.0 本项目双Token
适用场景 第三方授权(如“微信登录”) 自主应用用户认证
Token类型 授权码、Access Token、Refresh Token Access Token、Refresh Token
有状态 通常依赖DB Refresh Token有状态
颁发流程 两次交互(授权码换Token) 一次登录即颁发
三、核心数据结构设计
3.1 JWT Claims 定义
type Claims struct {
UserID uint `json:"user_id"`
Username string `json:"username"`
TokenType string `json:"token_type"` // "access" 或 "refresh"
UserType string `json:"user_type"` // "admin" 或 "app"
Sid string `json:"sid"` // 会话ID,关联Redis
jwt.RegisteredClaims
}
设计原理:
TokenType:防止Refresh Token被误用为Access Token,实现职责分离。
UserType:支持同一系统内多个终端(管理端、普通用户端)使用不同认证策略。
Sid:将JWT与后端会话存储绑定,从而获得主动吊销、多端管理的能力。
3.2 会话存储模型(Redis)
管理端与应用端采用不同策略:
管理端:每个会话独立存储,刷新时轮换Sid,增强防重放能力。
应用端:支持多设备登录限制,使用Set维护用户所有会话ID,可踢出最早登录设备。
这种设计兼顾了不同场景的需求——管理端要求高安全性,应用端更关注设备管理灵活性。
四、双Token的生命周期管理
4.1 颁发(登录)
用户登录成功后,服务端同时生成Access Token与Refresh Token,并将会话信息存入Redis。此时Sid随机生成,作为本次登录的唯一标识。
关键决策:是否存储Access Token到服务端?
我们的方案:仅存储Refresh Token对应的会话,Access Token保持完全无状态。
理由:降低Redis存储压力,避免每次请求都查询会话,提升性能。同时通过TokenType和短有效期控制风险。
4.2 验证(API请求)
中间件执行步骤:
提取Authorization: Bearer 。
解析JWT,校验签名和有效期。
确认TokenType == “access”。
(可选)从Redis校验会话是否存在——如果是高安全级别操作,可以额外检查。
一般建议:常规API仅做JWT校验,敏感操作(修改密码、支付等)再查Redis确认会话有效性。
4.3 刷新(无感续期)
刷新流程的核心设计:
客户端收到401后携带Refresh Token调用刷新接口。
服务端解析Refresh Token(允许过期解析,因为Access Token过期时Refresh Token可能也临近过期但未过)。
校验TokenType == “refresh”,并查Redis确认会话存在。
生成新的Token对,并根据策略决定是否轮换Sid。
管理端:轮换新Sid,删除旧会话。
应用端:可保留原Sid,实现多个Access Token共用一个Refresh会话。
为什么不直接延长Access Token?
因为短有效期Access Token是安全基石,延长它会扩大泄露影响。刷新机制的引入使得用户无需感知续期,而攻击者即使拿到过期的Access Token也无法续期(缺少Refresh Token)。
4.4 主动吊销
当用户修改密码、退出登录、账号被禁用时,需要让所有相关Token失效。方法:
删除Redis中对应的会话记录(按Sid或按UserID批量删除)。
下一次任何请求携带该会话的Token时,中间件可检查Redis发现会话不存在,拒绝访问。
这弥补了JWT无法主动失效的短板。
五、安全模型深度分析
5.1 威胁模型与防御策略
威胁 双Token防御措施
Access Token泄露 有效期短(1-2小时),泄露后攻击窗口有限。
Refresh Token泄露 仅用于刷新接口,暴露频率低;可绑定IP/设备指纹;存入安全存储(httpOnly Cookie/Keystore)。
重放攻击 管理端刷新时轮换Sid,使旧Access Token失效。
中间人攻击 强制HTTPS,防止Token在传输中被截获。
会话劫持(Cookie被盗用) 绑定HttpOnly + Secure + SameSite标志,降低XSS和CSRF风险。
无状态Token篡改 JWT使用HS256签名,服务端验证签名,防止内容篡改。
5.2 存储策略抉择
Web端最佳实践:
Access Token:内存(如变量、Vuex/Redux),页面刷新后丢失,需重新登录或使用Refresh Token恢复。这最大限度减少XSS窃取风险。
Refresh Token:httpOnly Cookie,不可被JavaScript读取,同时设置Secure和SameSite=Strict。
移动端最佳实践:
Access Token:内存或安全存储(如Android KeyStore、iOS Keychain)。
Refresh Token:必须存入Keychain/Keystore,防止恶意应用读取。
5.3 并发刷新与防雪崩
当多个请求同时因Access Token过期而返回401时,若不加控制,会导致大量刷新请求涌入。解决方案:
前端设置“正在刷新”标志,后续401请求进入队列等待。
仅第一个401触发刷新,成功后使用新Token重试队列中的所有请求。
刷新失败时,统一踢出到登录页。
六、不同端策略的差异设计
6.1 管理端 vs. 应用端
维度 管理端 应用端(普通用户)
安全等级 极高 中等
多设备登录 不允许或最多1-2台,且互踢 允许3-5台,可选踢出最早设备
Sid轮换策略 每次刷新均轮换,防重放 长期保持不变,简化实现
会话存储 单会话无Set 使用Set管理用户所有会话
超时时间 Refresh Token 较短(如7天) Refresh Token 较长(如30天)
6.2 “记住我”的实现
“记住我”本质上是延长Refresh Token的有效期,同时保持Access Token时长不变。服务端根据登录请求中的remember_me参数,在生成Refresh Token和存储Redis会话时使用不同的TTL。
需要注意的是,真正的“记住我”还应在客户端持久化Refresh Token(例如存入localStorage),但这会降低安全性。更好的做法是仍使用httpOnly Cookie,但延长服务端过期时间。
七、总结
双Token机制不是简单的“加一个Refresh Token”,而是一套关于安全、体验、架构的平衡艺术。
核心设计原则
职责分离:Access Token负责短期授权,Refresh Token负责长期续期。
混合状态:Access Token无状态以提升性能,Refresh Token有状态以支持管理。
最小暴露:Refresh Token仅在刷新接口传输,避免随每个API请求发出。
主动失效:通过Redis会话实现即时吊销能力。
落地检查清单
Access Token有效期 ≤ 2小时
Refresh Token存储在安全区域(httpOnly Cookie / 安全存储)
实现401自动刷新机制,并处理好并发请求
登录、注销、修改密码时能正确创建/删除Redis会话
对管理端实施更强的刷新策略(如Sid轮换)
强制HTTPS,防止中间人攻击
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)