ThinkPHP 8 实现 Token 刷新需构建前后端协同的续期链路,核心是中间件统一处理:读取并验证 refresh_token、校验设备指纹、生成新 access_token 并作废旧 token、通过响应头返回,且在认证通过后自动前置刷新。

ThinkPHP 8 实现 Token 刷新授权,关键不是“加个 refresh 接口就行”,而是构建一套前后端协同、状态可控、安全不裸奔的续期链路。核心要解决三个问题:谁来触发刷新、怎么判断该刷新、新 Token 怎么交还给前端且不影响业务响应。
刷新逻辑必须放在中间件里
不能把刷新写在控制器中——那样每个接口都要重复校验,容易漏判或污染返回结构。应新建中间件(如 app/middleware/TokenRefresh.php),在 handle() 中完成以下动作:
- 从请求头读取
X-Refresh-Token或Authorization: Bearer xxx(若复用同一 token 字段,需先区分 access/refresh) - 解析并验证 refresh_token:用
JWT::decode()显式指定['HS256'],校验iss、aud和签名,再检查是否在有效期内 - 查 Redis 或数据库确认该 refresh_token 未被注销、且绑定的设备指纹(如 UA+IP 哈希)与当前请求一致
- 验证通过后,生成新的 access_token(含原 uid、role、新 iat/exp),同时作废旧 refresh_token(删库 + 加入黑名单)
- 用
$response->withHeader('X-Access-Token', $newToken)注入新 token,不修改响应体
刷新时机要“无感”且“前置”
刷新不是等用户点按钮才触发,而是在每次合法访问受保护接口前自动完成:
- 认证中间件(如 AuthToken)校验 access_token 通过后,立即检查其剩余有效期(如
$exp - time() ) - 若即将过期,直接调用刷新逻辑生成新 token,并透传到响应头
- 确保该中间件在
app/middleware.php中排在「响应处理类」之前,否则 header 无法生效 - 避免并发刷新:对同一用户 ID 加 Redis 分布式锁(如
SET lock:refresh:{uid} 1 EX 10 NX)
refresh_token 存储必须加密+绑定
绝不能明文存数据库或丢进 localStorage。安全做法是:
立即学习“PHP免费学习笔记(深入)”;
- 入库前用
openssl_encrypt($raw, 'AES-256-CBC', $key, 0, $iv)加密,$key 从配置读取,不硬编码 - 关联字段至少包含:
user_id、device_fingerprint(建议 UA+IP 哈希)、created_at、expires_at - 数据库只存
refresh_token_hash = sha256($raw . $salt),原始值仅在内存中短暂存在 - 每次成功刷新后,旧 refresh_token 立即失效,新 token 设置 7 天有效期,并限制单用户最多 3 个有效 refresh_token
配合路由级中间件注册更灵活
不推荐全局注册刷新中间件,容易误刷登录页或公开接口。应在路由分组中精准绑定:
Route::group(['middleware' => ['jwt.auth', 'token.refresh']], function () { ... });- 排除不需要刷新的路径:
->except(['login', 'logout', 'public/info']) - 注意
except匹配的是完整路由名(如api.login)或 URL 路径(如/api/v1/login),不是方法名



















