长连接下jwt无感刷新必须由服务端主动推送控制帧、客户端用状态机原子切换,不可复用http /refresh接口;需独立传输refresh_token并校验jti与指纹,避免裸传和重放。

长连接场景下,Gin 本身不提供无感刷新能力——它只是 HTTP 框架,而“无感刷新”在 WebSocket/gRPC 等长连接中根本不能靠 Gin 中间件自动触发。你得先明确:当前是纯 HTTP REST 接口,还是已升级为 WebSocket/gRPC 长连接?两者实现逻辑完全不同,混用会直接导致 token is expired 频繁报错、用户静默断连。
HTTP 场景下用 Gin 实现 refresh_token 轮换
这是最常见也最容易落地的场景:用户发请求 → Access Token 过期 → Gin 中间件捕获 401 → 触发刷新 → 重放原请求。关键不是“怎么返回新 token”,而是“怎么避免并发刷新和旧 token 复用”。
- 刷新接口必须独立路由(如
POST /auth/refresh),且只接受refresh_token(从 Cookie 或 Authorization Bearer 中取),绝不接受access_token作为输入 - 解析
refresh_token后,必须校验其jti字段是否为 UUID,并查 Redis:refresh:{jti}是否存在且未被标记为已使用;不存在或已用,直接返回401 - 验证通过后,立刻执行
DEL refresh:{jti},再生成新access_token和新refresh_token,并把新jti写入 Redis,TTL 设为 7 天 - Gin 中间件里别用
c.Request.Body二次读取原始请求体——应只信任已解析过的token.Claims["jti"],否则高并发时可能因读取顺序错乱导致双签发 - 刷新成功后,必须清除旧 Cookie:
c.SetCookie("refresh_token", "", -1, "/", "example.com", true, true),再写入新值,否则前端下次仍可能带上失效 token 形成循环 401
WebSocket 连接里无法复用 Gin 的 /refresh 接口
WebSocket 握手完成后,HTTP 生命周期就结束了。Set-Cookie、X-Token-Expired 等响应头全部失效,Gin 中间件对后续帧毫无感知。此时调用 /auth/refresh 等同于开一个新 HTTP 连接,既破坏长连接语义,又无法同步更新服务端绑定的连接上下文。
- 建连阶段(HTTP Upgrade 请求)必须携带
refresh_token,服务端解析出jti后查 Redis:refresh:{user_id}:{jti},验证通过才允许升级 - 连接建立后,服务端需为该连接启动独立 goroutine,从 JWT
claims.ExpiresAt.Time计算剩余时间,用time.Until()启动time.AfterFunc(),提前 5 分钟推送控制帧(如{"type":"token_refresh_hint","at_exp":1747928880}) - 客户端收到后不能立刻覆盖当前 token,必须先解析新
access_token并验证签名、aud、iss,再原子替换内存中的 token 变量 - 服务端推送新 token 时,必须走专用通道:WebSocket 用自定义 opcode(如
0x0A),gRPC 用独立RefreshTokenRequest方法,严禁塞进业务消息体 - 连接关闭前务必调用
refreshTimer.Stop(),否则 timer 会持续触发并 panic 写已关闭连接
为什么不能把 refresh_token 存在 localStorage 里
前端若把 refresh_token 存 localStorage,等于放弃 HttpOnly 保护,XSS 攻击可直接窃取。更严重的是:多个标签页共享同一份 refresh_token,一旦某页刷新成功,其他页持有的仍是旧值,继续用就会触发 invalid jti 错误,导致用户被踢出。
- 正确做法是:仅在首次登录时通过
Set-Cookie设置HttpOnly refresh_token,前端 JS 完全不可读 - 所有刷新动作均由后端驱动:HTTP 场景靠中间件拦截 401 后自动调用
/auth/refresh;长连接靠服务端预推送控制帧 - 客户端只需维护一个
access_token字符串变量,刷新逻辑完全由服务端指令触发,前端只做校验与原子替换
真正容易被忽略的点是:长连接中 access_token 不是“可刷新资源”,而是“连接身份凭证”。它过期意味着整个连接的身份上下文已失效,服务端不能再让它发消息——你必须设计连接迁移或重鉴权机制,而不是幻想“换一个 token 就能续命”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











