JWT生成必须用openssl_pkey_get_private()加载私钥,不能直接传PEM字符串;需校验Header中alg字段与签名算法严格匹配(如RS256对应openssl_sign),且Base64Url解码、UTC时间戳校验等步骤缺一不可。

JWT生成时必须用openssl_pkey_get_private()加载私钥,不能直接传字符串
PHP原生JWT签名依赖OpenSSL扩展,常见错误是把PEM格式私钥内容(含-----BEGIN RSA PRIVATE KEY-----)直接当字符串传给openssl_sign()。这会导致签名失败或空结果。正确做法是先用openssl_pkey_get_private()解析密钥资源,再传入签名函数。
- 私钥文件路径需可读,且权限不能过于宽松(如600)
- 若私钥有密码保护,需在
openssl_pkey_get_private()第二个参数传入密码字符串 - 使用
file_get_contents()读取后直接传入会静默失败,无明确错误提示
验证JWT头部算法字段必须与实际签名方式严格匹配
JWT头部的alg字段(如RS256、HS256)不只是说明,它控制PHP签名验证时调用的底层算法。如果JWT是用RS256签发的,但代码中误用hash_hmac('sha256', ...)去验签,必然失败——而且不会报错,只会返回false。
- 验证前务必用
json_decode(base64_decode(...), true)解码Header,检查alg值 -
HS256用hash_hmac(),RS256必须用openssl_verify()配合公钥资源 - 部分第三方库(如firebase/php-jwt)会自动处理,但手动实现时这个判断点极易被跳过
base64url_decode()不能直接用base64_decode()替代
JWT的三段(Header、Payload、Signature)都使用Base64Url编码,它和标准Base64的区别在于:用-代替+、用_代替/,且不补=。直接用base64_decode()解码可能返回false或乱码,尤其在Signature段。
- 需手动替换后再解码:
str_replace(['-', '_'], ['+', '/'], $input),再补足=(长度模4) - PHP 7.4+ 可用
base64url_decode()(需启用ext-sodium),但多数生产环境仍需手写兼容逻辑 - 漏掉补
=会导致json_decode()对Payload解析失败,错误信息常为JSON_ERROR_SYNTAX
时间戳校验(exp、nbf)必须用UTC时间对比
JWT规范要求exp(过期)、nbf(生效前)等时间戳均为Unix时间戳(秒级),且以UTC为准。PHP默认time()返回的是服务器本地时区的时间戳,若服务器时区设为Asia/Shanghai,而JWT按UTC生成,就会出现16小时偏差。
立即学习“PHP免费学习笔记(深入)”;
- 验证前统一用
date_default_timezone_set('UTC'),或显式用gmmktime()生成当前UTC时间戳 - 不要依赖
date('U'),它受时区影响;应改用time()(PHP 5.1+已保证返回UTC时间戳) - 部分JWT库默认忽略时区,但手动实现时这个细节90%的人第一次都会踩坑



















