跳到主要内容
ecdsa.com
PHP

firebase/php-jwt

firebase/php-jwt,PHP 中安装量最大的 JWT 包:用 Key 对象锁定算法,拒绝过短的 HMAC 密钥,但不支持 ES512 和 PS384/512,不检查颁发者和受众,而且接受用 P-384 密钥签名的 ES256 令牌。
许可证
BSD-3-Clause
检查的版本
7.2.1 ·
源代码
github.com/firebase/php-jwt
文档
github.com/firebase/php-jwt#readme

兼容性概览

JOSE 与 JWT

JWS 算法
HS256HS384HS512RS256RS384RS512ES256ES384ES256KEdDSA

php-jwt 基于 openssl 扩展和 libsodium,用 JWT::encode() 和 JWT::decode() 编码和解码令牌。从第 6 版起,Key 对象——即密钥材料加算法——成为必需,因此算法来自你的代码,而不是来自令牌:一个 RS256 令牌和一个用 EC 密钥的 PEM 文本伪造的 HS256 令牌,用 new Key($pem, 'ES256') 验证时都报错 Incorrect key for this algorithm;未签名的令牌则报错 Algorithm not supported。这层防护只在由应用构造 Key 时才成立;new Key($pem, 'HS256') 就让伪造的令牌验证通过了。

7.2.1 版支持 HS256/384/512、RS256/384/512、ES256、ES384、ES256K 和 EdDSA。不支持 ES512(Algorithm not supported),PS256 需要安装 phpseclib/phpseclib,PS384 和 PS512 则完全不可用——颁发者使用这些算法时,其令牌需要用 web-token 或 lcobucci/jwt 处理。短于哈希长度的 HMAC 密钥会被拒绝(Provided key is too short)。

ES256 没有与 P-256 绑定:头部为 ES256、却用 P-384 密钥签名的令牌,由该库自己生成,也被它自己接受,尽管其他库会拒绝。ES256 签名会被转换为 JWS 要求的 64 字节原始 r‖s;DER 签名会报错 Signature verification failed。可以在 JWT 调试器中检查令牌的签名。

声明的处理非常简单。exp、nbf 和 iat 只在存在时才检查,JWT::$leeway 是默认为 0 的全局静态变量,而且完全不校验颁发者和受众——示例自行检查 exp 和 iss。基于 JWKS 验证时,JWK::parseKeySet() 把密钥集转换为 Key 对象;可以用 JWKS 检测查看提供方发布的密钥集。

事实及其依据的检查

以下内容均非抄自文档。检查脚本 content/libraries/verify/firebase-php-jwt/run.sh 于2026年10月20日在容器镜像 php:8.5.11-cli-trixie 中运行了 firebase/php-jwt 7.2.1;本页内容必须与它的输出一致,每条事实都列出了对应的输出行。

  • JWS 算法

    HS256HS384HS512RS256RS384RS512ES256ES384ES256KEdDSA

    HS256/384/512、RS256/384/512、ES256、ES384、ES256K 和 EdDSA 的签名与验证均成功;不支持 ES512 和 PS384/512,PS256 需要 phpseclib。

    检查记录
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES256K, EdDSA
    evidence jws-algorithms: PS256: DomainException: phpseclib/phpseclib is required for PS256 support
    evidence jws-algorithms: PS384: DomainException: Algorithm not supported
    evidence jws-algorithms: PS512: DomainException: Algorithm not supported
    evidence jws-algorithms: ES512: DomainException: Algorithm not supported
    evidence jws-algorithms: Ed25519: no key family
  • ES256 签名编码

    raw

    ES256 签名是 64 字节的原始 r‖s。

    检查记录
    fact jws-ecdsa-encoding = raw
  • JWS 中的 DER 签名

    rejected

    DER 编码的 ES256 签名会报错 SignatureInvalidException: Signature verification failed。

    检查记录
    fact jws-der-signature = rejected
    evidence jws-der-signature: Firebase\JWT\SignatureInvalidException: Signature verification failed
  • 算法允许列表

    required

    decode() 要求传入带算法的 Key 对象;只传密钥字符串会失败,用 new Key($ecPem, 'ES256') 验证 RS256 令牌会报错 Incorrect key for this algorithm。

    检查记录
    fact jwt-algorithm-allowlist = required
    evidence jwt-algorithm-allowlist: RS256 token against new Key(EC PEM, 'ES256'): UnexpectedValueException: Incorrect key for this algorithm
    evidence jwt-algorithm-allowlist: decode() with a bare key string: UnexpectedValueException: "kid" empty, unable to lookup correct key
  • 未签名的令牌(alg none)

    rejected

    未签名的令牌会报错 Algorithm not supported。

    检查记录
    fact jwt-alg-none = rejected
    evidence jwt-alg-none: UnexpectedValueException: Algorithm not supported
  • 把公钥用作 HMAC 密钥

    rejected

    以 PEM 文本作为密钥的 HS256 令牌,用 ES256 的 Key 验证失败,但 new Key($pem, 'HS256') 让它验证通过了。

    检查记录
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: Key(PEM, 'ES256'): UnexpectedValueException: Incorrect key for this algorithm
    evidence jwt-hmac-public-key: Key(PEM, 'HS256') built by the application: ok
  • 按 alg 检查曲线

    not-enforced

    以 ES256 为算法、用 P-384 密钥签名的令牌能够成功生成并通过验证。

    检查记录
    fact jwt-ecdsa-curve-check = not-enforced
    evidence jwt-ecdsa-curve-check: ok
  • 默认时钟偏差

    0s

    JWT::$leeway 为 0:三秒前过期的令牌被拒绝。

    检查记录
    fact jwt-clock-skew = 0s
    evidence jwt-clock-skew: JWT::$leeway = 0
  • 是否要求过期时间声明

    no

    不带 exp 的令牌也能成功解码。

    检查记录
    fact jwt-exp-required = no
  • 过短的 HMAC 密钥

    rejected

    5 字节的 HS256 密钥会报错 Provided key is too short。

    检查记录
    fact jwt-hmac-short-secret = rejected
    evidence jwt-hmac-short-secret: 5-byte HS256 key: DomainException: Provided key is too short

用法

签发 ES256 令牌,用锁定算法的 Key 以 30 秒容差解码,并显式检查 exp 和 iss。
<?php
require 'vendor/autoload.php';

use Firebase\JWT\JWT;
use Firebase\JWT\Key;

$private = openssl_pkey_new(['private_key_type' => OPENSSL_KEYTYPE_EC, 'curve_name' => 'prime256v1']);
$publicPem = openssl_pkey_get_details($private)['key'];

$token = JWT::encode(['sub' => 'user-42', 'iss' => 'https://auth.example.com', 'exp' => time() + 600], $private, 'ES256');

// The Key object pins the algorithm; leeway is a static setting, 0 by default.
JWT::$leeway = 30;
$claims = JWT::decode($token, new Key($publicPem, 'ES256'));

// php-jwt checks exp/nbf/iat only when present and never checks iss or aud: do it here.
if (!isset($claims->exp) || $claims->iss !== 'https://auth.example.com') {
    throw new UnexpectedValueException('unexpected claims');
}
echo 'sub: ', $claims->sub, "\n";

代码片段测试工具会在容器镜像 php:8.5.11-cli-trixie 中原样运行每个示例,只要输出不匹配,运行就会失败。

环境准备
curl -fsSL -o /tmp/composer.phar https://getcomposer.org/download/2.10.3/composer.phar && echo '7a2d379d5b8ffdaa028580ef26494c36d2feef4b178d3dd1473a4dbc5e17c8d6 /tmp/composer.phar' | sha256sum -c - >/dev/null && apt-get -qq update >/dev/null && apt-get -qq install -y unzip >/dev/null 2>&1 && COMPOSER_ALLOW_SUPERUSER=1 php /tmp/composer.phar require --quiet firebase/php-jwt:7.2.1
命令
php usage.php
输出必须匹配(正则表达式)
sub: user-42

库