这篇复盘记录一次 JWT 双票(AT/RT)治理的真实过程。我们要解决的问题很直接:如果刷新令牌在异常场景下被重复使用,系统能不能及时发现并收口。
说明:本文涉及的域名、IP、账号标识、日志字段、会话标识均为脱敏示例,不对应任何真实生产信息。
系统边界如下:
- 前端:Web SPA(AT 仅存放在内存)
- 后端:Go + Gin(鉴权与刷新)
- 会话层:Redis(令牌状态索引)
- 网关:HTTPS 反向代理
现象与触发条件
在储能云平台的日常安全演练中,我们针对海量边缘网关与前端看板的认证链路进行了渗透测试,其中重点模拟了“旧 RT 被拦截并重复提交”的极端情况。
旧方案虽然有 AT/RT 分层,但在刷新链路上缺少状态约束,导致旧 RT 在短时间窗口内仍可能被并发利用。
脱敏后的事件样式如下:
{
"event": "auth.refresh.reuse_detected",
"user_id_masked": "u-***39",
"session_id": "s-***b1",
"ip_masked": "10.**.**.21",
"action": "session_revoked"
}
排查与改造策略:保留 AT 无状态,强化 RT 治理
我们没有改变双票结构,而是在 RT 刷新路径补齐状态管理,核心目标是“可撤销、可审计、可回收”。
第一层:明确令牌职责
- AT 负责高频访问,走
Authorization: Bearer - RT 只通过 HttpOnly Cookie 传输
- AT 校验仍保持无状态,确保吞吐和扩展性
这样做的好处是:把控制成本留在 RT 通道,不影响 AT 的轻量访问特性。
第二层:将 RT 刷新改为单次可追踪流程
刷新链路采用“校验旧 RT -> 签发新 RT -> 更新状态 -> 失效旧 RT”的顺序,避免并发刷新出现双活。
Redis 键模型(脱敏示例):
rt:active:{jti} -> { user_id, session_id, exp } // 核心状态,TTL 跟随 RT 过期时间
rt:session:{session_id} -> Set<jti> // 冗余索引,用于会话级一键清理
rt:deny:{jti} -> 1 (TTL=剩余有效期) // 黑名单,拦截被重放的旧票据
user:sessions:{user_id} -> Set<session_id> // 用户级管控,支持“踢出其他设备”
对应约束:
- 同一 RT 只允许成功刷新一次
- 新 RT 生效后,旧 RT 立即进入 deny 列表
- 刷新过程加短时锁,避免并发竞态
并发防抖设计(结果复用窗口)
现代前端 SPA 往往会并发发起多个请求,这很容易导致旧 RT 同时触发多次刷新,进而把合法请求误判为重放。为此,我们设计了 5 秒复用窗口:
- 第一个请求拿到锁并完成 RT 轮换
- 轮换得到的 AT/RT 对会缓存 5 秒
- 相同 key 的并发请求在 5 秒内直接复用这组 AT/RT,不再触发二次轮换
- 超过窗口后恢复严格单次轮换策略
第三层:异常重放后的处置策略
一旦检测到 RT 重放,系统会触发会话级失效并记录审计事件,再由前端回到登录流程。
目标不是“继续尝试修补会话”,而是尽快结束风险会话。
第四层:把登出和高危操作接入服务端吊销
以下操作统一接入会话失效流程:
- 用户主动退出登录
- 管理员重置密码
- 用户状态调整为锁定或禁用
这一步把“只删浏览器 Cookie”升级为“服务端状态真实失效”。
根因总结
这次问题本质上是治理深度不够,而不是单点代码缺陷:
- 早期实现关注“签发与验签”,对令牌生命周期管理覆盖不足。
- 会话状态能力已有技术预留,但未完整接入刷新主链路。
改造后的收益
这次治理后,链路上最明显的变化有三点:
- 会话可以在服务端快速撤销
- RT 重放可以被识别并审计
- 安全事件可按用户与会话维度快速定位
对业务体验的影响可控:正常请求保持无感刷新,异常场景统一回到重新登录。
过程改进与行动项
1)把令牌生命周期校验纳入发版检查
上线前新增固定检查项:
- RT 单次使用验证
- 刷新并发一致性验证
- 登出/重置密码后的会话失效验证
2)统一安全审计字段
审计日志统一使用脱敏字段:user_id_masked、session_id、jti_prefix、ip_masked、ua_hash、risk_level。
3)固定开展重放演练
按月执行一次“RT 重放”演练,持续验证告警、失效和回收链路是否生效。
Phase 2 演进路线(Roadmap):从会话可控到上下文零信任
经过本次改造,我们已经完成 Phase 1 的核心目标:构建了 Redis 会话撤销链路与并发防重放机制。Refresh 流程会实时校验用户状态,账号进入锁定或禁用状态时会即时阻断新票据签发。
针对更高阶的 Token 劫持场景,Phase 2 规划继续向上下文零信任演进:
- 上下文感知风控:计划引入环境指纹(IP 段 + UA Hash)对比,当 RT 出现异常地域跳变时,自动执行 AT/RT 全量失效,并联动账号冻结与三方二次身份核验。
- 误杀率治理:考虑到工业现场可能出现 4G/5G 频繁切网、出口漂移等合法抖动,相关策略将先完成风控模型调优,再按灰度方式切入主链路。
结语
认证安全真正考验的,不是签发速度,而是异常发生后的收口能力。
这次改造最大的价值,是让 AT/RT 架构从“能用”走向“可治理”。