对新访问令牌的 PHP Google API OAuth2 JWT 请求显示“invalid_grant”

Posted

技术标签:

【中文标题】对新访问令牌的 PHP Google API OAuth2 JWT 请求显示“invalid_grant”【英文标题】:PHP Google API OAuth2 JWT request for new acces token says "invalid_grant" 【发布时间】:2016-10-30 23:36:02 【问题描述】:

简而言之,我想通过 Google API 上的服务帐户获取新的访问令牌。我使用了 OAuth 2.0 和 JWT 请求。我发现了很多类似的帖子,但没有一个回答我的问题。我所做的一切都符合Google OAuth 2.0 Server-to-Server request guide,但是当我发送 POST 请求时,我得到了“invalid_grant”的回报。任何想法为什么?这是我的代码:

$jwt_header_array =     array(  "alg"       => "RS256",
                                    "typ"       => "JWT");

    $jwt_claim_array =      array(  "iss"       => "upload-file@feedking-1355.iam.gserviceaccount.com",
                                    "scope"     => "https://www.googleapis.com/auth/drive.file",
                                    "aud"       => "https://www.googleapis.com/oauth2/v4/token",
                                    "exp"       => time()+3600,
                                    "iat"       => time());

    $jwt_header = base64_encode(json_encode($jwt_header_array));
    $jwt_claim = base64_encode(json_encode($jwt_claim_array));

    $key = "-----BEGIN PRIVATE KEY----- privat_key_downloaded_from_google_console -----END PRIVATE KEY-----\n";

    $pkeyid = openssl_pkey_get_private($key);
    openssl_sign($jwt_header.'.'.$jwt_claim, $jwt_signature, $pkeyid, "sha256");
    $jwt_signature = base64_encode($jwt_signature);

    $jwt = $jwt_header.'.'.$jwt_claim.'.'.$jwt_signature;

    echo $jwt.'<br /><br />';

    $url = 'https://www.googleapis.com/oauth2/v4/token';
    $query = 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion='.$jwt;

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
    $result = curl_exec($ch);

    var_dump($result);

【问题讨论】:

从技术上讲,您需要对 JWT 元素进行 base64url 编码,而不仅仅是对它们进行 base64 编码 这些编码很好,因为我得到了正确的 JWT 标头并在 Google 的示例中声明,并使用来自该示例的相同输入。唯一的问题是签名,我无法验证,因为密钥与示例中使用的不同。 【参考方案1】:

我找到了解决方案,而且非常明显。为什么不使用 Google Utils、Auth 和 Signer/P12 类?您可以使用 composer (google/apiclient) 获得整个包,此外,您还需要它来进行 Google API 身份验证(事实上我已经有了这个包,我哑了)。我将指导您完成整个过程:

新解决方案:

您将需要以下旧解决方案(第 1、2、3 阶段)中所述的 Google API 包和 Google 服务帐户。从这里只需复制粘贴完全基于 API 的代码并替换输入数据:

$client_email = 'file-upload-final@feedking-1355.iam.gserviceaccount.com';
$private_key = file_get_contents('p12_final.p12');
$scopes = array('https://www.googleapis.com/auth/drive.file');
$credentials = new Google_Auth_AssertionCredentials(
    $client_email,
    $scopes,
    $private_key
);

$client = new Google_Client();
$client->setAssertionCredentials($credentials);
if ($client->getAuth()->isAccessTokenExpired()) 
     $client->getAuth()->refreshTokenWithAssertion();

旧解决方案:

    创建一个Google Service account,因为这对于在未经用户进一步同意的情况下访问 Google API 至关重要。确保在创建它时选中“提供新私钥”和“P12”选项。如果需要,请检查其他选项。

    下载P12文件,上传到你的服务器/本地,并存储密码(此时通常是“notasecret”...)。

    如果您还没有,请使用 composer 或 GIT 安装 Google API 包。我更喜欢 composer,因为 GIT 版本缺少一些库,对我来说 Client 类有点错误(当然可能是我的错)。

    您只需使用正确的类来制作和编码 JWT 标头和播放负载,并签署证书。注意 P12 证书路径和播放负载中的正确输入(您可以从 Google Developer Console Credetials 页面获取所有内容,也可以在 Google OAuth 2.0 Server-to-Server Application 页面获取有关值的信息)。这是代码:

$header = array("typ" => "JWT", 
                "alg" => "RS256");

$playload = array(  "iss"       => "upload-file2@feedking-1355.iam.gserviceaccount.com", 
                    "scope"     => "https://www.googleapis.com/auth/drive.file",
                    "aud"       => "https://www.googleapis.com/oauth2/v4/token",
                    "exp"       => time()+3600,
                    "iat"       => time());

$segments = array();
$segments[] = Google_Utils::urlSafeB64Encode(json_encode($header));
$segments[] = Google_Utils::urlSafeB64Encode(json_encode($playload));
$signing_input = implode(".", $segments);
$signer = new Google_Signer_P12(file_get_contents('p12.p12', true), 'notasecret');
$signature = $signer->sign($signing_input);
$segments[] = Google_Utils::urlSafeB64Encode($signature);
$jwt = implode(".", $segments);
    使用 cURL 或您想要获取访问令牌的任何内容发送请求:
$url = 'https://www.googleapis.com/oauth2/v4/token';
$query = 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion='.$jwt;

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
$result = curl_exec($ch);
    最后,var_dump($result) 输出应该是类似的:

string(141) " "access_token": "ya29.Ci8QAzKz-U83RiaylDKgSDgH9XEVQrdBAQ5EBxMFUncN7WLfeGan0BCMJRdFJykC-g", "token_type": "Bearer", "expires_in": 3600 "

希望你喜欢它并且会投票,因为我在这个认证故事上搞砸了 2 天。 Google API 在某些情况下可以发挥神奇的作用,因此学习 inmho 是必不可少的。

【讨论】:

【参考方案2】:

您的代码中只有一个小问题,它将完美运行……原生 php base64_encode() 它与 API 的预期 base64_url_encode() 有点不同

代码如下:

function base64_url_encode(string $input) : string

    return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));

一切都会好起来的。

我知道已经有好几年了(从问题开始),Google 创建了可以由Composer 轻松下载和安装的库,但是…… 就我而言,我需要在 GA 的最后 7 天前进行访问。使用 Google 的原始库,我可以做到。但它是 66.8 MB 的文件,仅用于这个小问题。

我知道还有很多有用的库,但我不需要它们。我只需要在 GA 的最后 7 天前进行访问。这就是我可以用原生 PHP 和 OpenSSL 函数做的事情……大约 1 kB 的代码。

【讨论】:

以上是关于对新访问令牌的 PHP Google API OAuth2 JWT 请求显示“invalid_grant”的主要内容,如果未能解决你的问题,请参考以下文章

Google Api Php 客户端的刷新令牌

关于google API中访问令牌和刷新令牌的问题

访问令牌 Google API 的刷新令牌:R 代码

哪些 Google 应用脚本 API 需要 OAuth 访问令牌?

通过 API 访问 Google 照片的长期令牌?

使用 OAuth 刷新令牌获取新的访问令牌 - Google API