JWT刷新令牌不可直接续期,须用服务端存储的、绑定用户与设备的refresh_token换发新access_token,并立即作废旧refresh_token。

JWT刷新令牌必须用独立的 refresh_token 字段
直接在原access_token过期前“续期”是错的——JWT本身不可变,签发后就不能改有效期。真正安全的做法是:登录时同时下发一对令牌:access_token(短时效,如15分钟)和refresh_token(长时效,如7天),且refresh_token必须存服务端(如Redis),带绑定关系(用户ID + 设备指纹 + 一次性使用标记)。
常见错误现象:
• 前端把access_token拿去后端调用一个refresh接口,后端只校验签名就返回新token → 攻击者截获旧token就能无限刷
• refresh_token明文存在localStorage → XSS后直接被盗用
• 不校验refresh_token是否已被使用过 → 重放攻击可行
- 每次用
refresh_token换新access_token后,立即从Redis删掉该refresh_token,并生成新的配对 -
refresh_token必须设HttpOnly + Secure + SameSite=Strict的Cookie,禁止JS读取 - 后端验证时检查
refresh_token是否匹配当前用户+设备指纹(比如User-Agent哈希或前端传的device_id)
PHP里用firebase/php-jwt做校验要手动处理nbf/iat/exp
很多人以为JWT::decode()自动拦住过期token,其实它默认只校验签名,时间字段全靠自己判断。不显式传入validate参数或手动比对,exp过期了照样解码成功。
使用场景:用户带着access_token请求API,你得在中间件里快速拒绝已过期、未生效、签发时间异常的token。
立即学习“PHP免费学习笔记(深入)”;
- 必须传
$key和$allowed_algs,否则可能被算法混淆攻击(如none算法绕过) - 解码后立刻检查:
$payload->exp 、<code>$payload->nbf > time()、$payload->iat > time()(防未来签发) - 别用
date_default_timezone_set()临时切时区来“适配”时间差——统一用UTC存、用time()比,避免夏令时翻车
use Firebase\JWT\JWT;
$payload = JWT::decode($token, $secret, ['HS256']);
if ($payload->exp < time()) {
throw new Exception('Token expired');
}
refresh_token被重复使用时要主动封禁用户会话
同一个refresh_token被两次请求,说明要么前端误触发,要么token已泄露。不能只返回错误,必须立刻冻结关联的所有活跃会话(包括当前这个),否则攻击者仍能用旧access_token操作几分钟。
性能影响:每次刷新都要查Redis、删旧token、写新token、更新用户会话状态表——别省这步,用pipeline减少RTT。
- 查
refresh_token对应user_id和session_id,删掉该用户所有access_token缓存(按user_id:*通配) - 在数据库标记该用户“会话异常”,下次登录强制重新验证MFA或密码
- 记录日志字段必须包含
refresh_token_hash(SHA256)、ip、user_agent、used_at,方便溯源
前端发起刷新请求时容易漏掉401重试逻辑
很多前端只在登录后存token,没处理access_token中途过期。用户操作到一半弹401,直接登出体验极差。必须在HTTP拦截器里捕获401,自动用refresh_token换新token,再重放原请求。
容易踩的坑:
• 拦截器没做请求队列,多个并发请求同时401 → 多次调/refresh → 后端删掉第一个refresh_token,后面全失败
• 换完token没更新全局access_token变量,下个请求还是用旧的
- 用Promise锁机制:首次401触发刷新,后续401都等同一个Promise resolve后再重试
- 刷新成功后,除了更新Authorization Header,还要同步更新内存里的
access_token和expires_in时间戳 - 重放请求前检查URL是否属于白名单(比如不重放
/logout或/refresh自身)
refresh_token生命周期管理——删早了前端卡住,删晚了留攻击窗口,而时间窗口往往只有几秒。











