JWT生成必须用openssl_pkey_get_private()加载私钥而非直接传PEM字符串,因直接传入会导致openssl_sign()静默返回false;私钥权限需设为600,有密码时须传真实密码而非空字符串,且alg字段必须与验证函数严格匹配。

JWT生成必须用openssl_pkey_get_private()加载私钥,不能直接传PEM字符串
PHP 8.1+ 的 OpenSSL 扩展默认禁用弱算法,但更常见的失败点是私钥加载方式错误。直接把 -----BEGIN RSA PRIVATE KEY----- 这类字符串传给 openssl_sign() 会静默返回 false,且无明确报错。
- 正确做法:先用
openssl_pkey_get_private()解析私钥文件内容,得到资源句柄;再传入签名函数 - 私钥文件权限必须为
600,否则file_get_contents()可能读取失败(尤其在 CLI 模式下) - 若私钥有密码保护,
openssl_pkey_get_private($pem, $password)第二个参数不能为空字符串,必须传真实密码 - 别用
openssl_pkey_new()在运行时生成 RSA 密钥对——不同 PHP 版本行为不一致,导出格式易出错,应改用系统命令行生成
验证时 alg 字段必须与 openssl_verify() 或 hash_hmac() 严格匹配
JWT 头部的 alg 不是装饰字段,它决定底层调用哪个验证逻辑。写错就验不过,而且不报错,只返回 false。
- Header 中是
"alg": "RS256"→ 必须用openssl_verify()+ 公钥资源,不能用hash_hmac() - Header 中是
"alg": "HS256"→ 必须用hash_hmac('sha256', ...),密钥是原始字节(如random_bytes(32)),不是 base64 编码后的字符串 - 用
firebase/php-jwt时,JWT::decode($token, $key, ['RS256'])第三个参数不可省略,漏传或值不匹配会抛DomainException: Algorithm not allowed - 公钥必须是 PEM 格式,以
-----BEGIN PUBLIC KEY-----开头,中间不能有多余空格或换行;用openssl_pkey_get_public()加载失败时返回false,需提前检查
Base64Url 解码不能用 base64_decode() 直接替代
JWT 的三段都用 Base64Url 编码,和标准 Base64 有两个关键区别:+ 替换为 -、/ 替换为 _,且不补 =。直接用 base64_decode() 会导致解码失败或乱码,尤其在 Signature 段。
- 必须自己实现或复用兼容函数,例如:
str_replace(['-', '_'], ['+', '/'], $input)后再base64_decode(),并手动处理可能缺失的= - 第三方库(如
firebase/php-jwt)内部已封装该逻辑,但手动实现 JWT 时极易忽略这点 - Header 和 Payload 解码后必须用
json_decode(..., true),否则对象访问(如$header->alg)会出错
time() 和 exp/nbf 校验必须设为 UTC 且手动比对
JWT::decode() 默认只做签名和算法校验,exp、nbf、iat 等时间字段不会自动检查。PHP 8.1+ 环境下,本地开发时区与服务器不一致是过期判定失败的主因。
立即学习“PHP免费学习笔记(深入)”;
- 务必在脚本开头执行
date_default_timezone_set('UTC'),否则time()返回的是本地时间戳 - 验证逻辑必须显式写:
if (isset($decoded->exp) && $decoded->exp - 不要依赖
JWT::decode()的自动校验开关(如['verify_exp' => true]),v6+ 版本中该选项已移除,必须手动判断 -
exp和nbf都是 Unix 时间戳,单位秒,且必须是整数;前端传来的浮点时间戳(如 JS 的Date.now() / 1000)需floor()或(int)转换后再比较



















