错误信息
Firebase\JWT\SignatureInvalidException: Signature verification failed
也可能输出为
- PHP Fatal error: Uncaught Firebase\JWT\SignatureInvalidException: Signature verification failed
如果未被捕获,PHP 会打印“PHP Fatal error: Uncaught Firebase\JWT\SignatureInvalidException: Signature verification failed in …/JWT.php”。
firebase/php-jwt (PHP) — Firebase\JWT\SignatureInvalidException from JWT::decode
含义
php-jwt 已经解析了令牌,也接受了你所传密钥对应的算法,但签名检查返回了 false:要么密钥与令牌不匹配,要么令牌在签名后被改动过。`SignatureInvalidException` 是 `UnexpectedValueException` 的子类,因此通用的 catch 块可能会把它混在解析错误中,使它不易察觉。
原因
配置的密钥不是签名者的密钥
常见从另一个环境复制来的公钥,或者颁发者那边已经轮换掉的密钥。Firebase Auth 和 Google 签名 ID 令牌所用的密钥每隔几天就会更换。
未按 kid 从密钥集中选取密钥
常见直接取 JWKS 中第一个密钥的代码(例如 `reset(JWK::parseKeySet($jwks))`)能一直正常工作,直到颁发者改用密钥集中的另一个密钥签名。
各服务之间的 HMAC 密钥不一致
偶见对 HS256 来说,密钥字符串的任何差异——空白字符、从文件读取时末尾多出的换行符、base64 编码与原始字节之分——都会产生不同的 MAC。
令牌在签名后被改动
偶见头部或载荷被重新编码,或者令牌是用两个令牌的不同部分拼接而成的。
修复方法
- 1.
传入整个密钥集,让 php-jwt 按 kid 选择
`JWT::decode` 接受以 `kid` 为键的 `Key` 对象数组(来自 `JWK::parseKeySet`),也接受 `CachedKeySet`,后者在密钥轮换时还会自动刷新。
php use Firebase\JWT\CachedKeySet; use Firebase\JWT\JWT; use GuzzleHttp\Client; use GuzzleHttp\Psr7\HttpFactory; // Keys selected by the token's kid, fetched from the issuer's JWKS and cached (PSR-6). $keys = new CachedKeySet( 'https://issuer.example.com/.well-known/jwks.json', new Client(), new HttpFactory(), $cache, // any PSR-6 cache pool 3600, // cache lifetime in seconds true // rate-limit refetches on unknown kids ); $claims = JWT::decode($token, $keys); - 2.
比对密钥对的公钥和私钥
如果从颁发者私钥推导出的公钥与你配置的公钥不同,签名就不可能验证通过。
bash # Do the issuer's private key and your public key belong together? openssl pkey -in issuer-private.pem -pubout | sha256sum openssl pkey -pubin -in configured-public.pem | sha256sum