结论是:Gin集成JWT自动续期的关键在于“何时重签”和“如何安全地重签”,必须采用access_token+refresh_token双Token机制,其中access_token短时效(如15分钟)、仅存前端,refresh_token长时效(如7天)、严格存服务端Redis并支持撤销,严禁单Token无条件续期,否则破坏JWT无状态安全前提。

直接说结论:Gin 集成 JWT 做自动续期,关键不在“怎么生成 token”,而在于「何时重签」和「如何安全地重签」——access_token 过期后不强制跳登录页,但必须用 refresh_token 换新 access_token,且 refresh_token 本身要可撤销、有时效、存服务端(比如 Redis)。
为什么不能只靠单个 JWT 自动续期?
JWT 本质是无状态的签名字符串,一旦签发就无法中途失效(除非加黑名单或改密钥)。如果只用一个 access_token 并在每次请求时无条件延长过期时间,等于变相延长了盗取 token 后的攻击窗口——这违背 JWT 的安全前提。
常见错误现象:
- 中间件里检测到
access_token即将过期,直接调token.SignedString()生成新 token 返回 —— 用户无感,但泄露的旧 token 依然有效 - 把
refresh_token也存在前端 localStorage,和access_token一样明文传输 —— 等于把第二把钥匙也交出去 - 用
time.Now().Add(2 * time.Hour)简单延长过期时间,没校验原始签发时间或活跃窗口 —— 攻击者可反复刷出长期有效的 token
双 Token 设计:access_token + refresh_token 怎么配?
两个 token 职责分离,参数必须错开:
-
access_token:短时效(如15m),只存前端内存(httpOnly=false),不存 Redis;Payload 中必须带iat(签发时间)和jti(唯一 ID,用于防重放) -
refresh_token:长时效(如7d),只存服务端 Redis(key ="rt:<jti>"</jti>),且设 TTL;前端只保留其值,每次刷新都需携带,后端校验存在性 + 未被吊销 - 两者共用同一
SecretKey,但签名时用不同Claims结构 ——access_token不含用户密码字段,refresh_token可额外加设备指纹等约束
示例配置片段(internal/config/jwt.go):
通过 Palebluedot AI(PBD)-TokenRouter 的多模态图像生成端点(`/v1/chat/completions`)使用 TokenRouter 兼容的方式生成或编辑图像...
type JWTConfig struct {
SecretKey string `mapstructure:"secret_key"`
AccessExpire time.Duration `mapstructure:"access_expire"` // 15m
RefreshExpire time.Duration `mapstructure:"refresh_expire"` // 168h (7d)
MaxRefreshAge time.Duration `mapstructure:"max_refresh_age"` // 30d,refresh_token 最大可用天数
}
Gin 中间件里怎么判断是否该续期?
不是“过期就续”,而是“快过期 + refresh_token 有效 + 用户仍活跃”才触发。核心逻辑在认证中间件的 auth.go 里:
- 先解析
access_token,检查token.Valid == true且err == nil—— 注意:err == nil只代表格式合法,token.Valid才代表签名正确且未过期 - 若
token.Claims.(jwt.MapClaims)["exp"].(float64)距当前时间不足 5 分钟(比如expireAt - time.Now().Unix() ),进入续期流程 - 从 header 或 cookie 提取
refresh_token,查 Redis:key 是否存在、value 是否匹配当前用户jti、TTL 是否足够 - 全部通过才生成新
access_token,并更新refresh_token的 Redis TTL(模拟滚动续期)
关键点:续期响应必须返回新 access_token,但不要主动替换前端存储 —— 由前端决定是否覆盖(避免并发请求时覆盖错)。
刷新接口怎么设计才不被滥用?
/api/v1/refresh 接口不能只验证 refresh_token 存在,还要做三件事:
- 校验
refresh_token的iat是否早于time.Now().Add(-cfg.MaxRefreshAge)—— 防止拿一年前的 refresh_token 反复刷 - 检查 Redis 中该
refresh_token是否已被标记为revoked(比如用户主动登出时SET rt:<jti> revoked EX 86400</jti>) - 生成新
access_token时,jti必须重新生成(UUID),旧access_token自动失效(因jti不同);同时生成新refresh_token,旧的立即从 RedisDEL
容易被忽略的细节:前端调用刷新接口后,必须用新 refresh_token 替换本地存储 —— 否则下次续期会失败。这个动作不能由后端隐式完成,否则破坏幂等性。

















