应直接安装 firebase/php-jwt:^6.10,避免使用非官方维护的TP JWT插件;密钥须来自环境变量,签发需强制设置 iat/exp/jti,中间件必须放行 OPTIONS 请求。

直接装 firebase/php-jwt:^6.10,别碰 TP 封装的“JWT 插件”
ThinkPHP 官方不维护 JWT 认证组件,市面上所谓“TP JWT 插件”多数是简单封装、硬编码密钥、没处理 OPTIONS 预检、或依赖过时的 php-jwt 版本(比如 lcobucci/jwt 3.3),2026 年已严重脱节。最稳路径就是跳过中间层,直用官方维护的 firebase/php-jwt——它无依赖、轻量、文档清晰,且明确支持 PHP 7.4+ 和 TP6/TP8。
-
composer require firebase/php-jwt:^6.10是当前(2026)兼容性最好、无弃用警告的稳定版;^7.0要求 PHP 8.1+,且JWT::encode()第三个参数从string改成array,会直接报错 - 别执行
php think jwt:create或thans/tp-jwt-auth这类命令——它们生成的jwt.php配置文件把密钥、算法、有效期全塞进去,一改就要清缓存、重测逻辑,还容易漏掉leeway导致验签失败 - 环境变量才是密钥唯一可信来源:
$_ENV['JWT_SECRET'],不是配置文件,更不是代码里写死的'example_key'
手动封装 JwtService 类,而不是靠配置驱动
把签发、解析、校验逻辑收口到一个服务类里,比塞进 config/jwt.php 更可控、更易调试、也更容易加监控和埋点。
-
encode()必须强制设置iat、exp、jti:前端传来的 payload 不可信,不能让它决定过期时间或伪造唯一 ID -
decode()必须用new Key($secret, 'HS256')构造,不是传字符串——^6.10已废弃字符串密钥方式,否则抛TypeError - 验证失败统一 throw
InvalidArgumentException,中间件 catch 后直接返回401,避免分散处理异常分支 - 示例关键片段:
public function encode(array $payload): string {<br> $payload['iat'] = time();<br> $payload['exp'] = time() + 3600;<br> $payload['jti'] = bin2hex(random_bytes(16));<br> return JWT::encode($payload, $this->secret, 'HS256');<br>}
中间件必须放行 OPTIONS 请求,否则跨域直接卡死
这是 TP 项目上线后最常被骂“为什么登录成功但接口 401”的原因——前端发预检请求(OPTIONS),中间件没判断方法就直接走鉴权,结果因 header 没 Authorization 而拦截。
- 验证逻辑开头必须加:
if ($request->method() === 'OPTIONS') return $next($request); - 提取 token 只认
Authorization: Bearer xxx,不解析 query 或 cookie;别写成$request->header('authorization')(小写 key 在某些 SAPI 下取不到) - 别在路由分组里全局绑定中间件,优先在需要鉴权的控制器方法上单独注解
@middleware('checkJwt'),避免误伤开放接口
别碰 thans/tp-jwt-auth 和 zewail/think-api 这类扩展
它们看似省事,实则隐藏大量陷阱:前者生成的 .env 文件把密钥明文落盘,后者默认用 deviation => 60 却不校验 iat,导致时间不同步的设备反复验签失败;更关键的是,两者都未适配 TP8 的容器自动注入机制,$this->app->make(JwtService::class) 会报找不到绑定。
-
thans/tp-jwt-auth的php think jwt:create命令本质只是复制模板,没做任何密钥安全检查,JWT_SECRET=abc123这种弱密钥照单全收 -
zewail/think-api的JWT::attempt()内部仍调用lcobucci/jwt,而该库早在 2023 年就停止对 PHP 7.x 的安全更新 - 所有“一键生成 token”的封装,基本都忽略
jti防重放,也没提供刷新机制——真要 token 刷新,自己写refresh()方法比依赖插件更可靠










