Golang Token刷新机制的核心是安全作废旧token、防重放、避竞态;需用UUID作jti强校验、Redis原子标记已用、清旧Cookie、不查库、短时access token+定期refresh。

直接上结论:Golang 实现 Token 刷新机制,核心不是“怎么发新 token”,而是“怎么安全地作废旧 token + 防止重放 + 避免竞态”。光返回 access_token 和 refresh_token 两字段,90% 的实现会在生产环境出问题。
Refresh Token 必须带唯一标识(jti)且服务端强校验
很多开发者用 jwt.MapClaims 直接塞 user_id 和 exp 就完事,但这样无法主动作废单个 refresh token。一旦泄露,只能等它自然过期。
-
jti字段必须是服务端生成的 UUID(不能客户端传、不能用时间戳拼接),且写入 Redis 或数据库时以jti为 key 存储状态 - 刷新接口收到
refresh_token后,先jwt.Parse解析,再查jti是否存在且未被标记为已使用(used_at字段或 Redis SETNX) - 验证通过后,立即用
DEL或SET jti_used 1 EX 86400标记该 jti 已消费——这步漏掉,就等于允许无限次复用同一个 refresh token
刷新接口必须清旧 Cookie 并拒绝重复提交
如果用 HttpOnly Cookie 存 refresh_token,刷新成功后不清理旧值,前端下次请求仍可能带上已失效的 token,导致 401 后又触发刷新,形成循环。
- 刷新成功后,必须调用
c.SetCookie("refresh_token", "", -1, "/", "example.com", true, true)清除旧 Cookie - 同时在响应头里设置新 Cookie:
c.SetCookie("refresh_token", newRT, int(time.Hour*24*7).Seconds(), "/", "example.com", true, true) - 别在刷新逻辑里读原始请求体二次解析(比如从
c.Body()再取一次 token),应只信任已解析并校验过的token.Claims["jti"]——否则并发刷新时可能因查库顺序错乱导致双签发
Access Token 过期 ≠ 必须刷新;Refresh Token 过期 ≠ 立即登出
用户正在操作表单,Access Token 刚过期,前端不该直接跳登录页,而应静默用 Refresh Token 换新 Access Token。但这个“静默”有前提:
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
立即学习“go语言免费学习笔记(深入)”;
- Refresh Token 的
exp是硬边界,超了就拒,但建议额外加nbf(not before)和iat,防止客户端伪造未来时间 - 不要把
MaxRefresh(如 7 天)硬编码进 JWT 的exp,而应在服务端判断:当前时间 −iat> 7*24*3600,才拒绝刷新——这样后续可动态调整策略而不需重发 token - 前端无感刷新失败(如 401 + refresh 接口也返回 401),才触发登出;否则只替换 Authorization header 中的 token 值即可
别在刷新逻辑里查用户表或权限表
Refresh Token 本身应携带足够信息:至少 sub(用户 ID)、jti、exp、scope(如 ["read:profile", "write:post"])。验证通过后,直接从 claims 取值,别再走一遍 DB.Where("id = ?", claims["sub"]).First(&user)。
- 查库引入延迟和竞态:A、B 两个请求几乎同时用同一 refresh token 刷新,A 查库、B 查库、A 写新 token、B 写新 token,结果 B 覆盖了 A 的 fresh token,A 的下一次请求就会失败
- JWT 的设计哲学就是“无状态”,刷新环节查库,等于把 state 又拉回服务端,还失去了水平扩展能力
- 如果真需要动态权限(比如管理员中途禁用某用户),那就用 short-lived access token(15 分钟)+ 强制定期 refresh,而不是在 refresh 时查库
最常被忽略的一点:refresh token 的存储介质必须支持原子操作。Redis 的 SET key value EX 604800 NX 是安全的,但用 MySQL 的 INSERT IGNORE 或 REPLACE INTO 在高并发下可能因主键冲突导致静默失败——这时候 jti 校验就形同虚设。

















