长连接下JWT无感刷新必须由服务端主动推送指令并配合客户端状态机,不可依赖HTTP响应拦截或定时器;需通过独立控制帧传输refresh_token,用Redis共享user_id级jti状态实现并发安全,全程零DB调用、原子替换token并同步更新签名上下文与心跳。

长连接场景下 JWT Token 无感刷新不能靠前端定时器或响应拦截器实现——WebSocket 或 gRPC 流式连接中没有标准的 HTTP 响应头机制,X-Token-Expired 和 new-token 这类 header 完全不可用。必须由服务端主动推送 token 更新指令,并配合客户端状态机接管刷新流程。
长连接如何感知 access_token 即将过期
HTTP 场景靠中间件检查 ExpiresAt 并返回 401,但长连接没有“每次请求都走中间件”的机会。服务端需在连接建立时启动独立 goroutine,监听该连接绑定的 access_token 生命周期:
- 从 token 解析出
exp(必须是time.Time类型,不能存为 int64 时间戳后二次计算) - 用
time.Until(claims.ExpiresAt.Time)得到剩余秒数,启动time.AfterFunc(),提前 60 秒触发推送 - 推送消息格式必须带版本标识,例如
{"type": "token_refresh_hint", "at_exp": 1747928880},避免客户端误判为业务数据 - 别在 conn.WriteJSON() 前加锁等待刷新完成——要异步通知,否则阻塞整个连接收发
refresh_token 怎么在长连接里安全传输
不能把 refresh_token 放在 WebSocket message body 或 gRPC stream response 的任意字段里明文传。它必须满足三个条件:独立通道、防重放、单次有效。
- 传输必须走专用控制帧,比如 WebSocket 的
ping/pong扩展帧(自定义 opcode = 0x0A),或 gRPC 中定义RefreshTokenRequest专用 service method - payload 必须含
jti(服务端生成的 UUID)和fingerprint_hash(sha256(UserAgent + RemoteIP)),且服务端 Redis Key 为refresh:{jti} - 收到请求后第一件事是
DEL refresh:{jti},返回失败则直接断连,不解析 JWT —— 防止重放攻击利用网络延迟重复提交 - 成功后返回新
access_token和新refresh_token(注意:旧refresh_token已失效,新值不能复用)
并发刷新时如何避免多条连接互相覆盖
用户可能同时打开多个标签页或设备,每个都维持一条长连接。若它们在同一秒内触发刷新,会导致 Redis DEL 竞态失败、token 轮换错乱、客户端状态分裂。
立即学习“go语言免费学习笔记(深入)”;
- 所有长连接共享同一个
user_id绑定的刷新状态,Redis Key 应为refresh:active:{user_id},值为当前有效的jti - 刷新前先
GET refresh:active:{user_id},确认本连接的jti是否仍是活跃值;不是则说明已被其他连接抢占,直接断连 - 刷新成功后,用
SET refresh:active:{user_id} {new_jti} EX 604800(7 天 TTL)更新,而非SETEX—— 避免因网络抖动导致新值未写入而旧值过期 - 客户端收到新 token 后,必须原子替换内存中的
access_token,并丢弃所有 pending 的未发送消息(它们已携带旧签名,重发必失败)
为什么不能在长连接里复用 HTTP 的 /refresh 接口
看似省事,实则埋雷。HTTP 接口默认依赖 Cookie 或 Authorization header,而长连接无法可靠传递这些上下文。
- WebSocket 握手时虽可带 query 或 header,但后续帧不再携带,
refresh_token无法持续验证 - gRPC metadata 可传,但默认不加密,且中间代理(如 nginx)可能 strip 掉自定义 key
- 更关键的是:HTTP 接口通常查库或调鉴权中心,而长连接要求毫秒级响应,IO 等待会卡死流式通信
- 正确做法是:长连接专用刷新逻辑必须只依赖 Redis + JWT 解析,全程不查 DB、不调外部服务、不读 request.Body
最易被忽略的一点:长连接的 token 刷新不是“换一个新字符串”,而是要同步更新客户端所有待发消息的签名上下文、重置心跳计时器、并确保服务端路由层(如网关)已加载新 token 的白名单缓存——漏掉任一环,就会出现“token 已刷新,但下一条消息仍被拒”的静默失败。


















