Token过期后前端直接401因后端认证中间件未放行续期路径,需将续期逻辑前置、手动解析token、实现Redis黑名单、前端加刷新锁并原子化更新本地token。

Token过期后前端请求直接401,不走续期逻辑
核心原因是 ThinkPHP 的中间件或验证层在解析 Authorization 头时,一发现 token 签名失效或时间过期,就立刻返回 401,根本没机会进续期分支。这不是前端“没发刷新请求”,而是后端压根没给它这个机会。
- 确保续期逻辑写在认证中间件之前(比如自定义
CheckTokenMiddleware),且对特定路径(如/api/auth/refresh)放行 - 不要在
Auth::check()或Auth::user()里隐式触发校验——它们默认严格验签+时效,应改用Auth::guard()->setToken($token)->user()手动解析,跳过自动拦截 - 前端必须带原始 token(非刷新后的新 token)发起刷新请求,后端靠
jwt.refresh或自定义逻辑识别该请求并生成新 token
ThinkPHP JWT 续期时刷新 token 但旧 token 仍能用
这是典型的状态管理缺失:JWT 默认无状态,服务端不存 token 黑名单,只要签名有效、未过期,就一直认。所谓“刷新”,只是发个新 token,旧的还在有效期里照样通行。
- 必须配合黑名单机制:刷新成功后,把旧 token 的
jti和过期时间存入 Redis,键名为blacklist:<jti></jti>,TTL 设为原 token 剩余有效期 - 每次解析 token 前,先查
Cache::has('blacklist:'.$jti),命中则直接抛出TokenBlacklistedException - 别依赖
jwt.refresh自带的黑名单(tp-jwt 包的实现常有 bug 或未启用),手动控制更可靠
前端无感续期时并发请求导致重复刷新或 401 爆发
用户快速点击多个按钮,或页面加载触发多个 API,都可能同时携带即将过期的 token 发起请求。若每个请求都各自尝试刷新,轻则重复写 Redis,重则因竞态导致部分请求拿不到新 token 而失败。
前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。
- 前端需加刷新锁:用一个
Promise缓存正在执行的刷新请求,后续请求 await 同一个 Promise,避免多次调用 - 后端刷新接口必须幂等:同一旧 token 多次请求,应始终返回相同新 token(可缓存
refresh_token:<old_jti></old_jti>5 秒,避免重复生成) - 不要让所有请求都走刷新流程——只对返回
401 Expired的响应做判断,且需检查响应头中是否含X-Need-Refresh: true这类明确标识
tp6.1+ 使用 think-auth 或 jwt-auth 包时 refresh 接口 404 或报错 Class not found
常见于包未正确注册路由或服务提供者,尤其是手动安装而非 composer require 引入时,配置漏掉关键环节。
立即学习“PHP免费学习笔记(深入)”;
- 确认已执行
php think vendor:publish --force(如果用了topthink/think-auth)或按firebase/php-jwt+ 自定义逻辑方式实现,而非强依赖某个未维护的第三方 jwt 包 - 刷新路由必须显式定义,例如:
Route::post('auth/refresh', 'AuthController@refresh')->middleware('cors');,不能指望包自动注册 - 若用
Auth::guard('api'),确保 config/auth.php 中'api'guard 的provider和driver配置正确,否则Auth::guard()->refresh()会找不到方法


















