双token机制是兼顾安全与可用的唯一方案:accesstoken短期有效(如15分钟)保障安全,refreshtoken长期有效(如7天)实现无感续期,且必须存redis单次使用、绑定设备指纹、动态轮换并严格校验时间。

双令牌(accessToken + refreshToken)是唯一能兼顾安全与可用的方案,单 Token 续期在 Go 中无法规避并发刷新、重放和状态失控问题。
为什么 jwt.Parse 不校验过期时间就返回 Valid == true
因为 jwt.Parse 默认不启用时间验证逻辑。即使 payload 里写了 "exp",若没传 jwt.WithValidTime(),token.Valid 永远为 true,且 token.Claims 里的字段也不会被自动校验。
- 必须显式加选项:
jwt.Parse(tokenString, keyFunc, jwt.WithValidTime()) - 之后还要手动检查:
if err != nil || !token.Valid,不能只看err -
keyFunc应根据token.Header["kid"]动态选密钥,避免轮换后旧 Token 仍能通过校验 - 别直接断言
token.Claims是jwt.MapClaims—— 先类型断言再判空,否则 panic
refreshToken 必须存 Redis 且单次有效
JWT 本身无吊销能力,refreshToken 若纯无状态,一旦泄露就等于永久通行证。
- Redis key 建议格式:
refresh:{jti},value 存{user_id}:{fingerprint}(fingerprint用 UA + IP 或 device_id 哈希生成) - 验证时:先
GET,命中则立刻DEL,再生成新对并写入新jti - 禁止复用旧
refreshToken字符串 —— 每次刷新都应生成全新值 - 别把它塞进 Cookie 自动发送;前端需显式放在请求体(如
{"refresh_token": "..."})中携带
前端拦截器必须共享 Promise,不能各自重试
多个并发请求同时收到 401,若每个都调 /auth/refresh,服务端会生成多对新 Token,前端无法确定哪个有效,Authorization header 覆盖混乱,后续请求大概率 403。
- 维护一个全局
refreshPromise变量,首次 401 时触发刷新并缓存 Promise - 后续同批 401 全部
await refreshPromise,而非发新请求 - 刷新成功后,用新
accessToken重放原始请求;失败则清空登录态 - 长连接(WebSocket/gRPC)场景下,服务端需主动预推送新 Token(如 exp 前 5 分钟),不能等过期再响应
最易被忽略的一点:刷新接口返回的新 refreshToken 必须立即生效,旧值在 Redis 中已不可查 —— 很多实现只更新了新值却忘了删旧 key,导致一次泄露可无限续期。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











