我正在尝试使用Google和OpenID Connect实现联邦登录系统,并且我无法验证和解析从Google返回的JWT id令牌。我在这里遵循谷歌的文档。
根据文档的建议,我尝试使用现有的JWT库。GitHub上最流行的PHP版本似乎是PHP_JWT。问题似乎出在JWK密钥的格式上。
上面链接的Google文档说要从jwks_uri
端点获取密钥,如其发现文档中所示。该端点返回以下内容:
{
"keys": [
{
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"kid": "1771931eb0eb64eb97733e857685be153e079bb9",
"n": "AMNFQMNJw/EVwrYsyPTnEHWkaPinPb4ngc/SqD701aisFhbU9/wWoKADeFtwfBcWl1qjzIqhPorQElB+2mtiqUh3Qtaazt1x5wA9XnJDe6kjtMGm9nNLMilSVNBilAE8GIdbciMycISfOfL0WRaJrqpNxewNEVZjuYiGzOWahiDP",
"e": "AQAB"
},
{
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"kid": "7b3bc600209875d3c42ae277a0d018d1d21986ec",
"n": "AN2UvG5+hNEMIPIbnpPm+JQi6LFWXBPzg3Ltb3xkVmSTjVaCFWppw/ZYRBgpToGKZP9XJstlOE88SDUFSMZIkIqtLpnUqmZax2Zc2gjEB9PhmHSH3/tTmtZ1U0X6V+crqitZ2uc3NV78vCn9/s+WuPwk/gfKBG8Cirb0fgLmsPd9",
"e": "AQAB"
}
]
}
查看JWT类的decode
和verify
方法的源代码,似乎$keys
参数可以是一个数组,但它们期望数组键为kid
,数组值为:@param string|resource $key for HS*, a string key works. for RS*, must be a resource of an openssl public key
。拔出kid
属性并将它们用作数组键很简单,但是应该使用什么作为数组值呢?
从谷歌的JWKs文档中,看起来我们正在使用RS*
,但我不知道关键对象的哪一部分是resource of an openssl public key
。我试过使用整个stdClass对象和n
字符串,但在openssl_verify
步骤都失败了。该函数发出一个通知:"警告:openssl_verify():提供的密钥参数不能被强制转换为公钥"。
显然,我传入了错误的键,但正确的键是什么呢?
Google的库似乎使用不同的端点来获取密钥。该端点似乎返回一个证书数组。我需要用类似的东西来代替吗?如果是这样,为什么文档会告诉您使用jwks_uri
端点?
对于将来尝试这样做的人,我想提供一个完整的答案;
jwks_uri
JSON密钥中的n
和e
部分给出了模数和指数,可用于检索公钥(这是验证签名所需的全部)。这篇文章详细介绍了如何在纯PHP中完成:
openssl:如何从模数
中获取公钥然而,你应该意识到,在谷歌的jwks_uri
提供的JSON文档使用URL安全base64编码(即普通base64 +和/字符替换)。如果不考虑这一点,可能仍然会给您一个无效的证书。由于您提到使用php-jwt
,使用phpseclib从模数和指数中获取公钥的工作代码是:
$modulus = 'someencodedmodulusvalue';
$exponent = 'someencodedexponentvalue';
$rsa = new Crypt_RSA();
$modulus = new Math_BigInteger(JWT::urlsafeB64Decode($modulus), 256);
$exponent = new Math_BigInteger(JWT::urlsafeB64Decode($exponent), 256);
$rsa->loadKey(array('n' => $modulus, 'e' => $exponent));
$rsa->setPublicKey();
$pubKey = $rsa->getPublicKey();
两个端点包含相同的信息,但格式不同。
jwks_uri
端点给出了RSA公钥的模和指数值。您可以使用这两个值生成您在https://www.googleapis.com/oauth2/v1/certs上获得的PEM文件。