【问题标题】:How do I verify a Google idToken in PHP?如何在 PHP 中验证 Google idToken?
【发布时间】:2016-09-22 05:26:21
【问题描述】:

我已经为此工作太久了。我正在寻找截至 2016 年 9 月的工作示例,用于验证 Google idToken,例如

eyJhbGciOiJSUzI1NiIsImtpZCI6IjZjNzgxOTQyZDg0OWJhMmVjZGE4Y2VkYjcyZDM0MzU3ZmM5NWIzMjcifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhdWQiOiIxMDQ5MTQ4MTU2NTQ2LTk2YjFxcTJsNTJtODVtODB0ZHVoZHVma2RwODRtN2tuLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwic3ViIjoiMTEyNTk4NDgzNjQ2MjY1OTYxNTQwIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF6cCI6IjEwNDkxNDgxNTY1NDYtdjJwZjRlbGhzOGNwcXBlcWZkMzU5am5nOWs5aW5kcTQuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJlbWFpbCI6InRlc3R1c2VydGh4QGdtYWlsLmNvbSIsImlhdCI6MTQ3NDc1NDMzMiwiZXhwIjoxNDc0NzU3OTMyLCJuYW1lIjoiVGVzdCBVc2VyIiwicGljdHVyZSI6Imh0dHBzOi8vbGg0Lmdvb2dsZXVzZXJjb250ZW50LmNvbS8tU0dldkZZRDlaWFEvQUFBQUFBQUFBQUkvQUFBQUFBQUFBQUEvQVBhWEhoUmtuX1hEaEhNLTEzeVMwTUtBcFNrZG1zVEdYdy9zOTYtYy9waG90by5qcGciLCJnaXZlbl9uYW1lIjoiVGVzdCIsImZhbWlseV9uYW1lIjoiVXNlciIsImxvY2FsZSI6ImVuIn0.btukbBvhek6w14CrBVTGs8X9_IXIHZKpV1NzJ3OgbGUfmoRMirNGzZiFAgrR7COTeDJTamxRzojxxmXx6EEkQqNQcbyN8dO0PTuNt9pujQjLbFw_HBhIFJQaJSR3-tYPN-UtHGQ5JAAySsvCPapXbxyiKzTyvGYRSU65LmyNuiGxe6RQe1zHjq2ABJ4IPRqKPuFupnGRPWYy BSTPU7XQvtfhgyqA0BWZUfmCIFyDxQhvMaXNLTs01gnGVhcUDWZLi9vuUiKUlz3-aSSbwdfCMAljhBHnjpYO6341k5-qmgKkWawv8DX_nMEzntsCMCr664rP4wFEbsRB5ledM9Pc9Q

使用 Google 推荐的方式并为 jwks_uri 提取“accounts.google.com/.well-known/openid-configuration”并提取“www.googleapis.com/oauth2/v3/certs”,从而为

{ "kty": "RSA", "alg": "RS256", “使用”:“签名”, “孩子”:“6c781942d849ba2ecda8cedb72d34357fc95b327”, "n": "s1dt5wFFaYl-Bt7Yb7QgWEatLJfxwWDhbd5yvm2Z4d1PRgNVQa9kwOArQNoOJ-b-oZnXLVFsVASUXEAumGf1ip5TVCQmMBKqlchSDNuoZfoWdpCCX7jx4gNuS43pS6VqV3QDjWnoXRTHaUi5pZEbpAmWpOeG_CfmewNVwBXPFx8-mtvEdtxIrspX4ayXTViR4vHc7MhQhUxllFbocxMjJysDQuZV9wN3MI0lVtQdf52SKJwF3lhvWA9-WAEZ1q8wq-I93Sfte95RaFjDqCH--Sh-8DjhK4OvgItcEGd5QRHjdLvrayPwaDQbpMRN2n3BkVWIxKJubtRiSeWbawCklQ", “e”:“AQAB” }

如果我将令牌传递给https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=TOKEN,则会进行验证,但这不是一个真正的答案,因为他们不会经常更改,但每次进行额外的网络调用只是自找麻烦。

那么有人可以给我指出一个可行的例子吗?我试过phpseclib,但它从来没有验证过。在这一点上,我可能已经寻找了大约 40 个小时,而我已经束手无策了。

感谢任何帮助。

我的相关代码:

$modulus = "";
$exponent = "";
$token = $_POST['token'];
$pieces = explode(".", $token);
$header = json_decode(base64_decode(str_replace(['-','_'], ['+','/'], $pieces[0])), true);
$alg = $header['alg'];
$kid = $header['kid'];
$payload = base64_decode(str_replace(['-','_'], ['+','/'], $pieces[1]));
$signature = str_replace(['-','_'], ['+','/'], $pieces[2]);
//$signature = base64_decode(str_replace(['-','_'], ['+','/'], $pieces[2]));

if (testGoogleList($alg, $kid, $modulus, $exponent))
{

    echo "Found in list:  kid=".$kid."\n";
    echo "n: (base64URL)".$modulus."\n";
    echo "e: (base64URL)".$exponent."\n";
    $modulus = str_replace(['-','_'], ['+','/'], $modulus);
    $exponent = str_replace(['-','_'], ['+','/'], $exponent);
    echo "n: (base64)".$modulus."\n";
    echo "e: (base64)".$exponent."\n";
    $rsa = new Crypt_RSA();
    $rsa->setHash("sha256"); 
    $rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1); 
    $modulus = new Math_BigInteger($modulus, 256);
    $exponent = new Math_BigInteger($exponent, 256);
    echo "n: (BigInteger)".$modulus."\n";
    echo "e: (BigInteger)".$exponent."\n";
    $rsa->loadKey(array('n' => $modulus, 'e' => $exponent));
    $rsa->setPublicKey();
    $pubKey = $rsa->getPublicKey();
    echo "Public Key from phpseclib\n".$pubKey."\n";
    echo "--First openSSL error check--\n";
    while ($msg = openssl_error_string())
        echo $msg . "<br />\n";
    echo "--After First Error Check, before Verify--\n";
    $res = $rsa->verify($pieces[0].".".$pieces[1], $signature); 
    while ($msg = openssl_error_string())
        echo $msg . "<br />\n";
    echo "--Verify result: ".var_export($res, true)."--\n";
}

输出

在列表中找到:kid=6c781942d849ba2ecda8cedb72d34357fc95b327
n: (base64URL)s1dt5wFFaYl-Bt7Yb7QgWEatLJfxwWDhbd5yvm2Z4d1PRgNVQa9kwOArQNoOJ-b-oZnXLVFsVASUXEAumGf1ip5TVCQmMBKqlchSDNuoZfoWdpCCX7jx4gNuS43pS6VqV3QDjWnoXRTHaUi5pZEbpAmWpOeG_CfmewNVwBXPFx8-mtvEdtxIrspX4ayXTViR4vHc7MhQhUxllFbocxMjJysDQuZV9wN3MI0lVtQdf52SKJwF3lhvWA9-WAEZ1q8wq-I93Sfte95RaFjDqCH--Sh-8DjhK4OvgItcEGd5QRHjdLvrayPwaDQbpMRN2n3BkVWIxKJubtRiSeWbawCklQ
e: (base64URL)AQAB
n: (base64)s1dt5wFFaYl+Bt7Yb7QgWEatLJfxwWDhbd5yvm2Z4d1PRgNVQa9kwOArQNoOJ+b+oZnXLVFsVASUXEAumGf1ip5TVCQmMBKqlchSDNuoZfoWdpCCX7jx4gNuS43pS6VqV3QDjWnoXRTHaUi5pZEbpAmWpOeG/CfmewNVwBXPFx8+mtvEdtxIrspX4ayXTViR4vHc7MhQhUxllFbocxMjJysDQuZV9wN3MI0lVtQdf52SKJwF3lhvWA9+WAEZ1q8wq+I93Sfte95RaFjDqCH++Sh+8DjhK4OvgItcEGd5QRHjdLvrayPwaDQbpMRN2n3BkVWIxKJubtRiSeWbawCklQ
e: (base64)AQAB
n: (BigInteger)18674717054764783973087488855176842456138281065703345249166514684640666364313492818979675328236363014396820758462507776710767978395332237045824933690552916871072924852353561300648679961653291310130667565640227949181785672954620248276915721938277908962537175894062430220752771265500386404609948390377043762106166027544443459977210114747088393335234720657330424186435226141073425445733987857419933850994487913462193466159335385639996611717486282518255208499657362420183528330692236194252505592468150318350852955051377118157817611947817677975817359347998935961426571802421142861030565807099600656362069178972477827638867161671399657071319083914500667014214521757304661303525496653078786180348831678824969667950119891369610525474165187687495455755684504105433077872587114630537058768184460798470456362909589578101896361255070801
e: (大整数)1095844162
来自 phpseclib 的公钥
-----开始公钥-----
MIIBeDANBgkqhkiG9w0BAQEFAAOCAWUAMIIBYAKCAVZzMWR0NXdGRmFZbCtCdDdZ
YjdRZ1dFYXRMSmZ4d1dEaGJkNXl2bTJaNGQxUFJnTlZRYTlrd09BclFOb09KK2Ir
b1puWExWRnNWQVNVWEVBdW1HZjFpcDVUVkNRbU1CS3FsY2hTRE51b1pmb1dkcEND
WDdqeDRnTnVTNDNwUzZWcVYzUURqV25vWFJUSGFVaTVwWkVicEFtV3BPZUcvQ2Zt
ZXdOVndCWFBGeDgrbXR2RWR0eElyc3BYNGF5WRWWaVI0dkhjN01oUWhVeGxsRmJv
Y3hNakp5c0RRdVpWOXdom01JMGxWdFFkZjUyU0tKd0YzbGh2V0E5K1dBRVoxcTh3
cStJOTNTZnRlOTVSYUZqRHFDSCsrU2grOERqaEs0T3ZnSXRjRUdkNVFSSGpkTHZy
YXlQd2FEUWJwTVJOMm4zQmtWV0l4S0p1YnRSaVNlV2Jhd0NrbFECBEFRQUI=
-----结束公钥-----
--第一次openSSL错误检查--
--在第一次错误检查之后,在验证之前--
错误:0906D06C:PEM 例程:PEM_read_bio:没有起始行
--验证结果:假--

【问题讨论】:

  • 你能发布一些公钥应该能够解密的示例密文吗?没有它,就不可能测试密钥。另外,您能否发布一些 Google 身份文档的链接?我做了一个谷歌搜索,但我发现并没有谈论 RSA..
  • "google-identity" 是我能找到的最接近的标签。更好但不存在的一个是“google-token”或“google-idtoken”。我会查找文档。
  • 我找到的唯一文档已经过时(因此首先是问题),但它应该解释我在寻找什么。 (developers.google.com/identity/protocols/CrossClientAuth)、(android-developers.blogspot.com/2016/01/…) 和 (riskcompletefailure.com/2013/11/…) 都提供了我正在寻找的东西,但目前对我的 PHP 服务没有任何用处。
  • 另外,应该解密的部分在那个令牌中。有 3 件按句点分开。它采用标题“。”的形式。有效载荷“。”签名。因此,据我所知,签名应该适用于(标头“。”有效负载),但这对我来说甚至不是那么清楚。

标签: php phpseclib google-identity


【解决方案1】:

所以对于任何从搜索引擎来到这里的人:

我试图使用 Google ID 令牌来验证我的登录凭据是否为:
准确
不作假
能够被后端服务器检查
使用数学计算,因此我不必每次都查询 Google(增加延迟和 if-anything-can-go-wrong-it-will 效果)

我知道大多数人肯定能读懂这段代码,但我想把它打出来解释下一个愤怒的灵魂会发生什么。

您的第一部分可能会有所不同,因为我来自 Android 并且从那里开始相当直截了当。

我的过程是在 Android 中请求令牌。 (仅显示示例和相关部分的差异)

 GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        .requestIdToken(getString(R.string.client_id))
        .requestEmail()
        。建造();

从Activity Result中获取token(onActivityResult)

 GoogleSignInAccount acct = result.getSignInAccount();
    String idToken = acct.getIdToken();

令牌由 3 部分组成,以句点分隔,格式为“$header.$info.$signature”。我们将使用“$signature”验证“$header.$info”。

$header 包含有关加密的信息,例如(解码后):

{"alg":"RS256","kid":"6c781942d849ba2ecda8cedb72d34357fc95b327"}

所以使用的算法是“SHA-256, with RSA Encryption”,keystore中的Key ID是6c781942d849ba2ecda8cedb72d34357fc95b327。我们稍后会用到它。

通过 HTTP 将整个令牌传递给我的后端服务器

然后使用以下代码解码令牌,直接取自已接受的答案

include('Crypt/RSA.php');  //path to phpseclib

$modulus = "";
$exponent = "";
$token = $_POST['token'];
$pieces = explode(".", $token);

$data = $pieces[0].".".$pieces[1];
$signature = base64_decode(str_replace(['-','_'], ['+','/'], $pieces[2]));

$header = json_decode(base64_decode(str_replace(['-','_'], ['+','/'], $pieces[0])), true);
$alg = $header['alg'];
$kid = $header['kid'];

if (testGoogleList($alg, $kid, $modulus, $exponent))
{
    $modulus = base64_decode(str_replace(['-','_'], ['+','/'], $modulus));
    $exponent = base64_decode(str_replace(['-','_'], ['+','/'], $exponent));

    $rsa = new Crypt_RSA();
    $rsa->loadKey([
        'n' => new Math_BigInteger($modulus, 256),
        'e' => new Math_BigInteger($exponent, 256)
    ]);

    $rsa->setHash('sha256');
    $rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);

    if ($rsa->verify($data, $signature))
    {
        echo "VALID!!!!";
    } else {
        echo "NOT VALID :'(";
    }
}

我们之所以使用base64_decode(str_replace(['-','_'], ['+','/'], $VARIABLE)) 是因为它们以base64URL 的形式呈现,其中“+”更改为“-”,“/”更改为“_”。所以我们把它从 base64URL > base64 > unencoded (plain) text.

这是做什么的?

  • 我们从 $_POST 获取令牌(我称之为 $token)。
  • 然后我们将其拆分为多个部分。
  • 请记住,我们需要使用第三部分来解码前两个的对,用句点(“.”)分隔。 (“$signature”是“$header.$info”的加密签名)
  • 完全解码签名,从 base64URL 到未编码(纯)文本。 由于 Google 使用 JSON 存储密钥信息,因此 json_decode 标头并获取加密类型和密钥 id。
  • 我将它包装在一个函数中,但我的函数testGoogleList 基本上是这样工作的:

    1. 所以我们传入算法和密钥 ID。
    2. 我测试我的本地密钥缓存,看看我们需要的密钥是否已经缓存。
    3. 如果没有,我们继续这里,否则跳到第 4 步。

      • 然后,我们访问网络并在 (https://accounts.google.com/.well-known/openid-configuration) 获取 Google 的 open-id 配置页面,如果不能,请使用 get_file_contents() 或 CURL 方法。我不得不使用带有选项“curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANYSAFE);”的 CURL在我的 CURL 方法中,因为它没有正确尝试 HTTPS。
      • 那个页面是一个 JSON 编码的文本文件,所以 json_decode 它。
      • 我们他们抓住“jwks_uri”键并像我们上面那样抓住那个页面。
      • 其中包含一组 Google 当前用于公钥验证的密钥。我 json_decode 并将它们临时存储到一个数组中。
      • 截断旧缓存并重写集合。不要忘记flock(),以防时间真的很差。
      • 确保您的密钥在新集合中。
    4. 如果我们在缓存中找到键,我们会从中提取“n”(我们将其称为“模数”)和“e”(“指数”)片段并将它们传回。

  • 然后我们从 base64URL > base64 > 未加密(纯)文本中解码模数和指数片段。

  • 创建 Crypt_RSA 类的新实例。
  • 将您刚刚解密的数字作为新密钥加载到该类中,类型为 Math_BigInteger,这样我们就可以对巨型数字进行数学运算。 (第二个参数是基数,所以基数 256 是一个字节,如果我们使用 BIG 整数,请使用它)
  • 设置我们的哈希和签名模式以匹配我们从 Google 获得的模式。
  • 进行验证以确保我们拥有有效的密钥。

在这之后,你可以用它做什么。

再次感谢您的帮助,neubert!

【讨论】:

    【解决方案2】:

    问题在于您没有对任何相关内容进行 base64 解码。

    这对我有用(告诉我签名有效):

    <?php
    include('Crypt/RSA.php');
    
    $data = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjZjNzgxOTQyZDg0OWJhMmVjZGE4Y2VkYjcyZDM0MzU3ZmM5NWIzMjcifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhdWQiOiIxMDQ5MTQ4MTU2NTQ2LTk2YjFxcTJsNTJtODVtODB0ZHVoZHVma2RwODRtN2tuLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwic3ViIjoiMTEyNTk4NDgzNjQ2MjY1OTYxNTQwIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF6cCI6IjEwNDkxNDgxNTY1NDYtdjJwZjRlbGhzOGNwcXBlcWZkMzU5am5nOWs5aW5kcTQuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJlbWFpbCI6InRlc3R1c2VydGh4QGdtYWlsLmNvbSIsImlhdCI6MTQ3NDc1NDMzMiwiZXhwIjoxNDc0NzU3OTMyLCJuYW1lIjoiVGVzdCBVc2VyIiwicGljdHVyZSI6Imh0dHBzOi8vbGg0Lmdvb2dsZXVzZXJjb250ZW50LmNvbS8tU0dldkZZRDlaWFEvQUFBQUFBQUFBQUkvQUFBQUFBQUFBQUEvQVBhWEhoUmtuX1hEaEhNLTEzeVMwTUtBcFNrZG1zVEdYdy9zOTYtYy9waG90by5qcGciLCJnaXZlbl9uYW1lIjoiVGVzdCIsImZhbWlseV9uYW1lIjoiVXNlciIsImxvY2FsZSI6ImVuIn0';
    $signature = 'btukbBvhek6w14CrBVTGs8X9_IXIHZKpV1NzJ3OgbGUfmoRMirNGzZiFAgrR7COTeDJTamxRzojxxmXx6EEkQqNQcbyN8dO0PTuNt9pujQjLbFw_HBhIFJQaJSR3-tYPN-UtHGQ5JAAySsvCPapXbxyiKzTyvGYRSU65LmyNuiGxe6RQe1zHjq2ABJ4IPRqKPuFupnGRPWYyBSTPU7XQvtfhgyqA0BWZUfmCIFyDxQhvMaXNLTs01gnGVhcUDWZLi9vuUiKUlz3-aSSbwdfCMAljhBHnjpYO6341k5-qmgKkWawv8DX_nMEzntsCMCr664rP4wFEbsRB5ledM9Pc9Q';
    $signature = str_replace(['-','_'], ['+','/'], $signature);
    $signature = base64_decode($signature);
    
    $n = 's1dt5wFFaYl-Bt7Yb7QgWEatLJfxwWDhbd5yvm2Z4d1PRgNVQa9kwOArQNoOJ-b-oZnXLVFsVASUXEAumGf1ip5TVCQmMBKqlchSDNuoZfoWdpCCX7jx4gNuS43pS6VqV3QDjWnoXRTHaUi5pZEbpAmWpOeG_CfmewNVwBXPFx8-mtvEdtxIrspX4ayXTViR4vHc7MhQhUxllFbocxMjJysDQuZV9wN3MI0lVtQdf52SKJwF3lhvWA9-WAEZ1q8wq-I93Sfte95RaFjDqCH--Sh-8DjhK4OvgItcEGd5QRHjdLvrayPwaDQbpMRN2n3BkVWIxKJubtRiSeWbawCklQ';
    $n = str_replace(['-','_'], ['+','/'], $n);
    $n = base64_decode($n);
    
    $e = 'AQAB';
    $e = base64_decode($e);
    
    $rsa = new Crypt_RSA();
    $rsa->loadKey([
        'n' => new Math_BigInteger($n, 256),
        'e' => new Math_BigInteger($e, 256)
    ]);
    
    $rsa->setHash('sha256');
    $rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
    
    echo $rsa->verify($data, $signature) ?
        'valid' :
        'invalid';
    

    【讨论】:

    • 天啊。所以我忘记在 mod 和 exp 上做一个 base64_decode,即使我在输出中将它们标记为这样。叹。我知道这将是一件愚蠢的事情。谢谢。
    猜你喜欢
    • 2015-09-05
    • 2019-03-09
    • 1970-01-01
    • 2019-09-11
    • 1970-01-01
    • 2019-09-30
    • 1970-01-01
    • 1970-01-01
    • 2018-11-10
    相关资源
    最近更新 更多