对新访问令牌的 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”的主要内容,如果未能解决你的问题,请参考以下文章