必须用服务端状态控制+滑动窗口+redis原子操作实现token续期,直接修改exp字段或重签token会导致无限续期风险;旧token需入redis黑名单,新token需重生成jti并独立存储refresh_token。

不能只靠改 exp 字段续期,必须用服务端状态控制 + 滑动窗口判断 + Redis 原子操作,否则等于把过期权交给客户端。
为什么直接重签 Token 是危险操作
很多人在中间件里解码旧 Token、把 exp 改成 time() + 3600 再签名返回——这会让攻击者截获任意有效 Token 后无限续期。JWT 一旦签发就不能变,所谓“续期”本质是服务端主动发新凭证,同时让旧凭证失效。
- 旧 Token 的
jti必须进 Redis 黑名单(key 为blacklist:<jti></jti>),TTL 设为原剩余有效期 + 300 秒缓冲 - 新 Token 的
jti要重新生成,不能复用旧值 -
refresh_token绝对不能塞进 access Token 的 payload,它必须独立存储、绑定设备指纹、单次使用
ThinkPHP 中间件里怎么安全触发滑动续期
续期动作必须发生在认证通过后、业务逻辑执行前,且不能污染响应体。推荐在 app/middleware/AuthToken.php 的 handle() 方法里做:
- 先调
$this->auth->check(),但要允许“即将过期”的 Token(比如剩余 ≤ 600 秒)仍视为有效 - 验证通过后,读取
$payload = $this->auth->getPayload(),提取iat和exp - 若
$payload['exp'] - time() ,则构造新 payload:<code>['iat' => time(), 'exp' => time() + 7200],保留原uid、type等字段 - 用
JWT::encode()生成新 Token,并用SETEX token:sha256($oldToken) $newToken 7500写入 Redis(比 JWTexp多 300 秒) - 不主动返回新 Token 给前端,除非接口文档明确要求响应头带
X-Access-Token
Redis 存 Token 状态时三个关键细节
单纯靠 JWT 自包含无法吊销或滑动刷新,Redis 是必须项,但键名、过期、并发都容易出错:
- 键名别用原始 Token,用
sha256($token)或base64_encode(urlencode($token)),避免特殊字符和长度溢出 - 不要对每个 Key 单独
EXPIRE,而要用SETEX一次性写入值和过期时间,防止系统时间偏差导致提前失效 - 高并发下多个请求同时续期同一 Token,需用
SET lock:token:<sha256> "1" NX EX 3</sha256>加分布式锁,避免重复生成和 Redis 写冲突
真正难的不是生成新 Token,而是确保每次续期都原子性地完成「查旧状态 → 删旧黑名单 → 写新 Token → 更新用户会话」这一整套动作。漏掉任意一环,就可能让已注销用户还能继续操作,或者活跃用户被误踢出。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











