使用 firebase/php-jwt 生成和验证 jwt 最稳妥,它正确处理算法、时间校验、header/payload 分离及 leeway 容错;需注意密钥强度、敏感信息不入 payload、手动截取 bearer 前缀、显式指定算法数组、decode 返回对象而非数组,且 jwt 无法主动作废,需配合黑名单或短时效+刷新机制。

PHP 生成 JWT Token 用 firebase/php-jwt 最稳
不用自己手写 Base64Url 编码或签名逻辑,firebase/php-jwt 是目前 PHP 社区最被验证过的实现。它处理了密钥类型(HS256 / RS256)、时间戳校验、header payload 分离等细节,自己造轮子容易漏掉 nbf(not before)或 leeway 时间容错,导致 Token 在边缘时间点失效。
安装:
composer require firebase/php-jwt
生成示例(HS256):
$payload = [
'user_id' => 123,
'exp' => time() + 3600,
'iat' => time(),
];
$token = \Firebase\JWT\JWT::encode($payload, 'your-secret-key', 'HS256');
注意点:
-
exp和iat必须是 int 类型时间戳,传字符串会静默失败 - 密钥长度不足会影响
HS256安全性,建议至少 32 字节随机字符串 - 别把敏感字段(如密码、手机号)塞进 payload,JWT 是可解码的,不是加密容器
验证 JWT 时必须设 leeway 防止服务器时间偏差
常见错误:Token 到期报 ExpiredException,但本地测试明明没超时。本质是服务器之间时间不同步,哪怕差 2 秒,exp 校验就挂了。
立即学习“PHP免费学习笔记(深入)”;
正确做法是设置宽容秒数:
\Firebase\JWT\JWT::$leeway = 5; // 允许前后 5 秒误差 $decoded = \Firebase\JWT\JWT::decode($token, 'your-secret-key', ['HS256']);
其他关键校验项:
- 必须显式传入算法数组(
['HS256']),否则可能被篡改 header 算法绕过签名检查 -
decode()返回对象,不是数组;要取字段得用$decoded->user_id,不是$decoded['user_id'] - 如果用
RS256,第二个参数是OpenSSLAsymmetricKey或 PEM 字符串,不是普通字符串密钥
Bearer Token 解析要手动截掉 Bearer 前缀
HTTP 请求头里通常是 Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...,直接拿整个值去 decode() 会报 DomainException: Signature verification failed —— 因为开头多了 Bearer 7 个字符。
安全提取方式:
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (strpos($authHeader, 'Bearer ') === 0) {
$token = substr($authHeader, 7);
} else {
http_response_code(401);
exit('Invalid auth header');
}
补充提醒:
- 别依赖
getallheaders(),在 CGI/FastCGI 下可能不可用,优先读$_SERVER - 空格、换行、制表符都可能导致
substr错位,建议加trim() - 不校验 header 是否存在就直接
substr,会触发Notice: Uninitialized string offset
JWT 不适合登出和权限实时变更
Token 一旦签发就无法主动作废,服务端没状态。用户点击“退出登录”,只是前端删了 localStorage 里的 token,但这个 token 在有效期内仍能继续访问接口。
可行缓解方案:
- 维护一个 Redis 黑名单(
blacklist:token:{sha256}),验证前先查是否存在,过期时间设为原 Token 剩余有效期 - 把权限信息(如角色、菜单)放在数据库,每次请求用
user_id查最新权限,而不是全塞进 JWT - 缩短
exp时间(如 15 分钟),配合前端自动刷新机制(用 refresh token 换新 access token)
真正难处理的是:用户在 A 设备登出,B 设备 Token 还在有效期内,且权限已变——这种场景 JWT 天然不覆盖,得靠额外机制兜底。











