应使用 composer 安装 firebase/php-jwt 库,通过其 JWT::encode() 和 JWT::decode() 方法生成与解析 Token,禁止手写签名或用 ThinkPHP 内置方法拼接。

JWT Token生成时该用哪个ThinkPHP内置方法封装
ThinkPHP本身不内置JWT实现,直接调用 firebase/php-jwt 是最稳妥的选择。别试图用 think\facade\Cache 或 think\helper\Str 自己拼token——签名算法、时间戳校验、base64url编码这些细节极易出错。
实操建议:
- 用
composer require firebase/php-jwt安装,别抄网上手写HS256签名的“精简版” - 密钥必须从配置读取(如
config('jwt.secret')),禁止硬编码在控制器里 - payload中务必包含
iat(签发时间)和exp(过期时间),否则JWT::decode()会因缺少验证字段报DomainException - 别把用户密码、手机号等敏感字段塞进payload——JWT是base64编码,不是加密,客户端可解码
中间件里解析Token失败的常见报错及对应处理
90%的鉴权中断来自中间件里 JWT::decode() 抛异常,但错误类型不同,处理方式完全不同。
常见错误现象与应对:
立即学习“PHP免费学习笔记(深入)”;
-
UnexpectedValueException: Signature verification failed→ 密钥不一致,检查config/jwt.php中的secret是否和生成时用的完全相同(注意空格、换行) -
DomainException: Invalid token format→ 请求头Authorization值不是Bearer xxxxx格式,先用str_starts_with($auth, 'Bearer ')做前置校验 -
ExpiredException→ 不要直接返回401,先查数据库确认用户是否被禁用或token是否被拉黑(需配合Redis存储已注销token的jti) - 静默失败(无报错但
$request->user为空)→ 检查中间件是否漏了return $next($request);,ThinkPHP中间件必须显式返回
如何让Auth类支持JWT同时兼容原有Session登录
不能直接替换 think\Auth,ThinkPHP的RBAC权限控制依赖其 check() 方法返回布尔值。JWT鉴权必须桥接到同一套权限判断逻辑里。
关键做法:
- 新建
app\common\service\JwtAuth类,实现check($rule, $uid = null)方法,内部调用原think\Auth::check(),但把$uid从token payload中提取(而非session) - 在全局中间件里,统一将解析出的用户ID写入
$request->withAttribute('uid', $uid),后续所有业务层通过$request->getAttribute('uid')获取,不依赖session或cookie - 避免在模型事件或命令行任务中调用JWT相关逻辑——没有HTTP头,
Authorization不存在,会直接崩
Redis存储jti做Token黑名单时的键名设计陷阱
JWT无法主动失效,靠jti+Redis实现“登出即失效”,但键名设计不对会导致缓存击穿或误删。
正确实践:
- 键名格式必须为
jwt:blacklist:jti(例如jwt:blacklist:abc123xyz),不要带用户ID前缀——jti本身已是全局唯一标识 - 过期时间必须设为
exp - iat + 3600(比token多留1小时),否则token刚过期,黑名单就没了,重放攻击仍可能成功 - 别用
SETNX单独设值,要用SETEX一步到位,避免并发时多个请求同时写入导致TTL不一致 - 每次请求都得先查
EXISTS jwt:blacklist:jti,命中则直接返回401,别等decode完再校验——性能差且违反“快速失败”原则
JWT鉴权真正难的不是生成和解析,而是状态管理:token刷新时机、多端登录互踢、jti生成规则是否真唯一、Redis故障时的降级策略——这些地方没日志、没监控,线上出问题根本没法回溯。


















