长连接场景下jwt无法用set-cookie刷新,因websocket/grpc握手后无法再发http头;需服务端预刷新token(如exp前5分钟推送),客户端校验后原子替换,禁用refresh token在长连接中复用。

长连接场景下 JWT 无法简单用 Set-Cookie 刷新的问题
WebSocket 或 gRPC 流式连接中,HTTP 响应头(包括 Set-Cookie)只在握手阶段生效,后续帧无法携带新 Token。这意味着你不能像 REST API 那样靠中间件拦截 401 后重发带新 Authorization 头的请求来刷新 Token。
真实问题在于:客户端持有旧 Token,服务端因过期拒绝后续消息,但又无法“推”新 Token 给客户端——除非你主动设计双向通信通道。
- 常见错误现象:
token is expired错误频繁出现,用户被强制断连重登 - 关键约束:长连接生命周期远超 JWT
exp(比如 24h 连接,Token 仅 30min 有效) - 不能依赖客户端定时轮询刷新(破坏长连接语义,增加无效流量)
服务端需在 Token 过期前主动下发新 Token 的时机控制
核心不是“等过期再处理”,而是“在过期窗口内预刷新”。建议将 Token 的 exp 和刷新触发点解耦:比如 Token 设为 30 分钟过期,但服务端在剩余 5 分钟时就通过业务消息或自定义控制帧推送新 Token。
Go 实现上,不要用全局定时器轮询所有连接,而应在每个连接的读写协程中维护一个 refreshTimer:
// 示例:在 WebSocket 连接初始化后启动刷新倒计时
conn.refreshTimer = time.AfterFunc(time.Minute*25, func() {
newToken, _ := genJWT(conn.UserID, time.Minute*30)
conn.WriteJSON(map[string]string{"type": "token_refresh", "token": newToken})
// 重置 timer,下次在新 exp 前 5 分钟触发
conn.refreshTimer.Reset(time.Minute * 25)
})
- 必须用
Reset()而非新建 timer,避免 goroutine 泄漏 - 时间偏移量(如 25min)应小于 Token 有效期,且留出网络延迟余量
- 若连接断开,务必调用
refreshTimer.Stop(),否则 timer 会持续触发并 panic 写已关闭连接
客户端如何安全接收并原子切换 Token
客户端收到刷新消息后,不能立刻覆盖当前 Token——万一新 Token 解析失败或网络中断,会导致后续所有请求 401。必须做校验 + 原子替换。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
典型做法是双 Token 缓存 + 校验钩子:
// JS 客户端示例(WebSocket onmessage)
if (data.type === 'token_refresh') {
try {
jwtDecode(data.token); // 纯前端校验 signature & exp
localStorage.setItem('jwt_token', data.token); // 原子写入
} catch (e) {
console.warn('invalid refresh token, keep using old');
}
}
- Golang 客户端(如使用
gorilla/websocket)同样需先调用ParseWithClaims()验证新 Token 合法性,再更新内存变量 - 避免直接替换
http.Header中的Authorization字段——gRPC 流或 WebSocket 自定义协议需显式透传新 Token - 若使用 TLS + 双向认证,Token 刷新可与证书续期解耦,别混为一谈
为什么不能把 Refresh Token 放在长连接里反复用
短生命周期 JWT + 长期有效的 Refresh Token 是 Web 传统方案,但在长连接中直接复用 Refresh Token 极其危险:一旦泄露,攻击者可无限次换取新 Access Token,且服务端难以及时吊销。
更稳妥的做法是:Refresh Token 仅用于初始连接建立时的认证,之后全程用预刷新的 Access Token,服务端不提供任何 “/refresh” 接口给长连接通道。
- 服务端应记录每个连接的 Token 签发序列号(
jti),并在每次刷新时生成新jti,便于按连接粒度主动作废 - 禁止在 WebSocket 消息体中传输明文 Refresh Token——它不该出现在长连接数据流里
- 如果业务强依赖 Refresh Token,至少要用一次性的、绑定连接 ID 的临时码替代原始 Refresh Token
最易被忽略的是连接复用场景:同一个用户多端登录时,服务端要能识别并踢掉旧连接,否则新 Token 刷新可能被旧连接“劫持”。这需要结合连接 ID 与用户 ID 做服务端状态映射,不能只靠 Token 本身。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










