直接安装 firebase/php-jwt:^6.10 最稳妥,手动封装 JwtService 类(密钥从环境变量读、固定 HS256、强制设置 iat/exp/jti),中间件验证时放行 OPTIONS 请求并仅从 Authorization 头提取 Bearer token。

直接装 firebase/php-jwt 就够了,别碰那些 TP 封装的“JWT 插件”
ThinkPHP 官方不维护 JWT 认证组件,市面上所谓“TP JWT 插件”多数是简单封装或已过时(比如依赖 php-jwt 旧版、硬编码密钥、没处理 token 刷新)。最稳的方式是跳过中间层,直接用官方维护的 firebase/php-jwt 库——它轻量、无依赖、文档清晰,且兼容 PHP 7.4+ 和 TP6/TP8。
执行安装命令即可:
composer require firebase/php-jwt:^6.10
注意版本号:^6.10 是目前(2024)与 TP6/TP8 兼容性最好、无弃用警告的稳定版。别用 ^7.0,它要求 PHP 8.1+,且 API 有 breaking change(比如 JWT::encode() 第三个参数从 string 改为 array)。
手动封装一个 JwtService 类,比用配置文件更可控
TP 的配置驱动式 JWT 扩展常把密钥、算法、有效期全塞进 jwt.php,结果一改配置就要清缓存、重测逻辑,还容易漏掉 leeway(时间偏移容错)导致验签失败。不如自己写个服务类,把关键逻辑收口:
立即学习“PHP免费学习笔记(深入)”;
-
$key从环境变量读($_ENV['JWT_SECRET']),不硬编码也不进配置文件 -
alg固定用HS256(除非你真有非对称签名需求) - 签发时强制设置
iat、exp、jti,避免前端传脏数据 - 验证失败统一抛
InvalidArgumentException,便于中间件捕获并返回 401
示例核心方法:
namespace app\service;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class JwtService
{
private string $secret;
public function __construct()
{
$this->secret = $_ENV['JWT_SECRET'] ?? 'your_strong_secret_here';
}
public function encode(array $payload): string
{
$payload['iat'] = time();
$payload['exp'] = time() + 3600; // 默认 1h
$payload['jti'] = bin2hex(random_bytes(16));
return JWT::encode($payload, $this->secret, 'HS256');
}
public function decode(string $token): object
{
return JWT::decode($token, new Key($this->secret, 'HS256'));
}
}
中间件里验证 token,但必须忽略 OPTIONS 请求
TP 的 middleware.php 或路由分组里加 JWT 验证时,常见错误是没放过预检请求(OPTIONS),导致跨域时前端卡在 401。验证逻辑不能只看 header 有没有 Authorization,还得先判断请求方法:
- 如果是
OPTIONS,直接return $next($request) - 否则才提取
Bearer xxx中的 token,并调用JwtService::decode() - 解码成功后,把用户 ID 写入
$request->withAttr('uid', $payload->uid),后续控制器可直接取
别在中间件里做数据库查用户——JWT 本身应携带必要字段(如 uid、role),查库是额外权限校验的事,和认证分离。
前端传 token 必须用 Authorization: Bearer ,TP 不自动识别其他头
有些教程教你在 header 里传 X-Token 或 token,然后在中间件里手动读 —— 这纯属给自己加戏。firebase/php-jwt 不管你怎么传,它只负责解码;但 TP 的请求对象默认只从标准 Authorization 头解析 Bearer token。如果你非要自定义 header,就得自己写逻辑提取,还容易和 CORS、Nginx 代理规则冲突。
所以前端发请求时,务必这样写:
fetch('/api/user', {
headers: {
'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...'
}
})
后端中间件里用 $request->header('authorization') 拿到字符串后,用 str_replace('Bearer ', '', $auth) 剥离前缀即可。别试图兼容多种格式,JWT 规范就认这个。
密钥长度和 token 刷新是真正容易被忽略的点:HS256 要求密钥至少 256 bit(32 字节),用短密码会降级成 HS128;而 token 过期后,别在前端自动刷新——得走独立的 /api/token/refresh 接口,用短期有效的 refresh token 换新 access token,否则就失去 JWT 的无状态优势。











