JWT刷新是用服务端可控的refresh_token换新access_token,而非续期旧token;refresh_token须独立存储、哈希校验、单次使用并绑定设备指纹,否则将导致安全风险。

JWT刷新不是“续期”,而是用服务端可控的凭证换新令牌。直接改旧token的exp再签名等于放弃安全前提。
refresh_token必须独立存储且不可复用
很多人把refresh_token当成access token的附属字段塞进payload,这是危险操作。它不该被客户端解析,只该由服务端密钥校验+Redis比对。
- 每次刷新必须生成全新
jti,存入Redis并设置TTL(建议略长于access token有效期,如15分钟+30秒) - 验证时先查
rt:$hash是否存在,存在才签发新access token,并立即DEL原key - 不用数据库就别硬上——Redis pipeline可一次完成查、删、写,避免竞态
- 前端传来的
refresh_token必须经hash_hmac('sha256', $token, $_ENV['JWT_REFRESH_SECRET'])哈希后再查,防爆破
JWT::decode()默认不校验时间字段
调用JWT::decode()后拿到的$payload,哪怕exp已过期,也不会自动抛ExpiredException——除非你显式传入验证参数或手动比对。
- 必须自己写:
if ($payload->exp -
iat和nbf同理,漏判会导致“未来签发”或“未生效即使用”漏洞 - 别信
date_default_timezone_set()临时切时区——统一用time()比UTC时间戳,避免夏令时翻车 - PHP 7.4+默认JSON decode不会把
exp转成float,但自定义decoder可能出问题,检查is_int($payload->exp)
并发刷新导致401的静默替换方案
两个请求同时携带同一access token,A还没收到响应,B已刷新并使旧token失效,A直接401——这不是bug,是设计缺陷。
立即学习“PHP免费学习笔记(深入)”;
- 必须采用「双Token + 时间差」:access token设15分钟,refresh token设7天,且刷新接口返回一对新token
- 前端需原子性替换本地两个值(不能先存access再存refresh,中间有窗口)
- 服务端对同一
user_id加Redis锁(SET lock:uid:123 "1" NX EX 5),防止高并发下重复签发 - 前端收到401时,区分错误类型:
invalid_token走登录页,refresh_expired强制重新登录
真正难的不是写JWT::encode(),而是让每个refresh_token只生效一次、绑定设备指纹、并在泄露时能主动冻结所有会话。这些逻辑一旦漏掉,JWT就从安全机制变成攻击跳板。



















