Refresh Token必须独立于Access Token管理,设7–30天过期、存Redis并绑定fingerprint、每次刷新作废旧Token且重设滑动TTL,校验时须查Redis+显式验exp,接口需明确接收X-Refresh-Token头。

JWT刷新时为什么Access Token过期后Refresh Token也失效了
因为多数人把Refresh Token存到了内存或没设独立过期时间,导致它和Access Token共用同一套生命周期逻辑。Refresh Token必须比Access Token活得久,且不能依赖Access Token的签发状态来判断自身有效性。
实操建议:
- Refresh Token的
exp字段要设为7–30天(视安全等级而定),Access Token只设15–60分钟 - 不要用同一个
jwt.Parse函数校验两种Token——Access Token可检查exp和iss,Refresh Token还必须查数据库里是否已被标记为revoked - 生成Refresh Token时别用
jwt.SigningMethodHS256硬编码密钥,改用独立密钥(比如REFRESH_SECRET环境变量)
Go里怎么安全存储和验证Refresh Token
直接存在Redis里最常见,但容易漏掉两件事:一是没设滑动过期(sliding expiration),二是没做绑定校验(比如绑定user_agent或ip)。一旦Refresh Token泄露,攻击者就能无限续期。
实操建议:
- Redis Key格式建议为
refresh:,其中: fingerprint_hash由User-Agent + IP前两段哈希生成 - 每次成功刷新后,旧Refresh Token立刻
DEL,新Token用SETEX设值,并带滑动过期(例如每次刷新都重设TTL为7天) - 验证时先查Redis是否存在,再用
jwt.ParseWithClaims解析,且必须显式检查claims.VerifyExpiresAt(time.Now().UTC(), true)
gin-gonic/gin里实现双Token刷新接口的坑
很多人在POST /auth/refresh里直接调c.GetHeader("Authorization")拿Refresh Token,但忘了前端通常不会自动带上它——Access Token走Authorization: Bearer xxx,Refresh Token得走另一个头,比如X-Refresh-Token,否则跨请求根本传不过去。
将 WCAG 2 AA 无障碍标准应用于设计系统、UI 组件和网页——通过可运行的验证器进行对比度计算,采用设计 token 模式使颜色天生可访问,设定排版和焦点状态规则,并提供语义检查清单。适用于构建或审查 UI。
实操建议:
- 接口接收Refresh Token必须明确指定Header名,比如
c.Request.Header.Get("X-Refresh-Token"),别假设它和Access Token共用一个Header - 返回新Token时,别只返回JSON体,还要清旧Cookie(如果用了HttpOnly Cookie存Refresh Token):
c.SetCookie("refresh_token", "", -1, "/", "example.com", true, true) - 别在刷新逻辑里复用
user.ID以外的任何原始请求数据——Refresh Token本身应包含足够信息(如sub、jti),避免二次查库引入竞态
Refresh Token被重复使用时如何检测和拦截
标准做法是“一换一”:每次刷新都生成新Refresh Token并作废旧的。但如果用户开两个标签页同时点刷新,就可能触发两次请求,导致第二个请求失败——这不是Bug,是设计使然;但得让客户端能友好重试,而不是报500。
实操建议:
- Redis里存的是
refresh:(jti即JWT唯一ID),不是refresh:,确保每个Token可独立吊销 - 刷新接口返回
401 Unauthorized而非400,当客户端收到401且确认自己刚提交过Refresh Token,就该清空本地Token并跳登录页 - 数据库里不一定要建
refresh_tokens表,但Redis里必须保留jti到user_id的映射,用于快速反查归属
真正难的不是生成两个Token,而是让Refresh Token的生命周期管理像数据库事务一样可靠:一次刷新,要么全成,要么全不成,中间状态不能对外可见。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










