双Token是Go微服务JWT落地的强制前提,单Token续期在高并发下必然引发三类故障;ParseAccessToken与ParseRefreshToken必须严格隔离密钥与结构体;Refresh Token须绑定设备指纹并原子消费,禁用jwt.MapClaims;HTTP头需分离Authorization与X-Refresh-Token;前端需共享刷新Promise避免并发冲突。

双Token不是“可选优化”,而是Go微服务中JWT落地的强制前提;单Token续期在高并发下必然出现并发刷新、旧Refresh Token复用、表单提交中断三类真实故障。
ParseAccessToken 和 ParseRefreshToken 必须完全隔离
共用一个解析函数但不区分密钥或 Claims 结构,会直接导致权限越界——比如把 Refresh Token 当 Access Token 解析后放行 admin 接口。错误常表现为 token.Valid == true 但 claims["scope"] 为空或类型错乱。
-
ParseAccessToken()只接受accessSecret,校验exp和iss,返回user_id和基础权限字段(如role),不读取jti或fingerprint -
ParseRefreshToken()必须显式使用refreshSecret,且只接受自定义结构体(含jti、fingerprint字段),调用claims.VerifyExpiresAt(now, true)严格比对过期时间 - 绝对禁用
jwt.MapClaims解析 Refresh Token:它绕过字段类型校验,jti缺失或被传为int会导致防重放逻辑彻底失效
Refresh Token 必须绑定设备指纹并原子消费
JWT 本身无吊销能力,不落 Redis = 允许无限续期。泄露一个 Refresh Token 就等于永久通行证,这不是假设,是生产环境已复现的漏洞。
- Redis Key 格式固定为
refresh:{user_id}:{fingerprint_hash},其中fingerprint_hash是sha256(fmt.Sprintf("%s:%s", r.UserAgent(), realIP)),IP 不可省略 - 签发时用
SETEX写入,TTL 设为硬性值(如7 * 24 * time.Hour),禁用EXPIRE滑动更新——否则攻击者可不断延长有效期 - 刷新接口第一行必须执行
DEL refresh:{user_id}:{fingerprint_hash};返回0表示该设备已被登出或换绑,直接拒掉,不再解析 JWT - 成功生成新 Token 对后,才写入新 Key;整个流程不查用户表、不读
request.Body里的user_id——唯一可信来源是解析出的claims["user_id"]和claims["jti"]
HTTP Header 必须分离 Authorization 和 X-Refresh-Token
把 Refresh Token 塞进 Authorization: Bearer xxx 会导致中间件误判,尤其当 Access Token 过期而 Refresh Token 仍有效时,鉴权层可能直接拒绝而非转发到刷新路由。
- Access Token 放
Authorization: Bearer - Refresh Token 单独放
X-Refresh-Token:,避免中间件混淆 - 别把它塞进 Cookie 自动发送;前端需显式放在请求体或 header 中携带,防止 CSRF 场景下被自动提交
- 刷新接口返回的新
refreshToken必须立即生效,旧值在 Redis 中已不可查——很多实现只更新了新值却忘了删旧 key,导致一次泄露可无限续期
前端拦截器必须共享 Promise,不能各自重试
多个并发请求同时收到 401,若每个都独立调 /auth/refresh,服务端 Redis 的 DEL 原子性会让仅一个成功,其余全失败,前端则陷入“刷新失败 → 清态 → 表单丢失”死循环。
- 维护一个全局
refreshPromise变量,首次401时触发刷新并缓存 Promise - 后续同批
401全部await refreshPromise,而非发新请求 - 刷新成功后,用新 Token 重放原始请求;失败则清空登录态
- 长连接(WebSocket/gRPC)场景下,服务端需主动预推送新 Token(如
exp前 5 分钟),不能等过期再响应——握手后无法再发 HTTP 头,靠客户端轮询会破坏连接语义
最易被忽略的是刷新链路中的状态一致性:Redis key 删除、新 Token 签发、旧 Token 失效这三步必须构成原子操作边界,任何一步失败都应回滚或明确拒掉,而不是“尽力而为”。生产环境里,90% 的 Token 泄露问题不是来自加密弱,而是来自状态管理松散。











