Google API OAuth 2.0 身份验证返回“invalid_grant” - 可能的私钥签名格式问题?

Posted

技术标签:

【中文标题】Google API OAuth 2.0 身份验证返回“invalid_grant” - 可能的私钥签名格式问题?【英文标题】:Google API OAuth 2.0 authentication returning "invalid_grant" - possible private key signature format issue? 【发布时间】:2014-11-19 07:55:02 【问题描述】:

我正在尝试连接到需要 OAuth 2.0 身份验证的 Google API。第一步需要我生成一个 JSON Web Token (JWT):https://developers.google.com/accounts/docs/OAuth2ServiceAccount?hl=ru#creatingjwt,格式如下:

Base64url encoded header.
Base64url encoded claim set.
Base64url encoded signature

我按照文档中的所有说明进行操作,但继续收到“invalid_grant”错误。我怀疑这与最后(签名)部分有关。根据文档:

"计算签名时必须使用JWT头中的签名算法。Google OAuth 2.0授权服务器唯一支持的签名算法是RSA使用SHA-256哈希算法。这在alg字段中表示为RS256 JWT 标头。”

“使用从 Google Developers Console 获得的私钥,使用 SHA256withRSA(也称为带有 SHA-256 哈希函数的 RSASSA-PKCS1-V1_5-SIGN)对输入的 UTF-8 表示进行签名。输出将是字节数组。”

“签名必须是 Base64url 编码的。”

我直接从 Google Dev Console 复制了私钥(包括“BEGIN PRIVATE KEY”部分)并对其进行了 base64 编码。这是我正在使用的代码:

$time = time();
$url = 'https://accounts.google.com/o/oauth2/token';
$assertion = base64_encode('"alg":"RS256","typ":"JWT"').'.';
$assertion .= base64_encode('
   "iss":"'.$this->configs['client_id'].'",
   "scope":"https://www.googleapis.com/auth/youtube",   
   "aud":"'.$url.'",
   "exp":'.($time+3600).',
   "iat":'.$time.'
  ').'.';
$assertion .= base64_encode('[-----BEGIN PRIVATE KEY-----privateKeyHere-----END PRIVATE KEY-----\n]');
$headers = array(
      'Host: accounts.google.com',
      'Content-Type: application/x-www-form-urlencoded'
);
$fields = array(
   'grant_type' => urlencode('urn:ietf:params:oauth:grant-type:jwt-bearer'),
   'assertion'  => urlencode($assertion)
);

$fields_string = '';
foreach($fields as $key => $value) $fields_string .= '&'.$key.'='.$value;
$fields_string = substr($fields_string, 1);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url );
curl_setopt($ch, CURLOPT_HEADER, TRUE );
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers );
curl_setopt($ch, CURLOPT_POST, count($fields));
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1 );
$result = curl_exec($ch);
curl_close($ch);
var_dump($result);

关于为什么返回“invalid_grant”错误的任何想法?

【问题讨论】:

【参考方案1】:

你有两个问题:

第一个:PHP 函数 base64_encode 不是 JWT 规范所要求的 URL 安全。 要获取 Base64Url 字符串,请使用以下函数:

function base64url_encode($data)

    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');

将您的 base64_encode 呼叫替换为 base64url_encode

第二个:您永远不会签署您的数据!您只需添加您的私钥,而不是标头和声明集的签名。 使用以下函数:

function sign($input, $private_key)

    $signature = null;
    openssl_sign($input, $signature, $private_key, "SHA256");
    return $signature;

然后替换

$assertion .= base64_encode('
  "iss":"'.$this->configs['client_id'].'",
  "scope":"https://www.googleapis.com/auth/youtube",   
  "aud":"'.$url.'",
  "exp":'.($time+3600).',
  "iat":'.$time.'
').'.';
$assertion .= base64_encode('[-----BEGIN PRIVATE KEY-----privateKeyHere-----END PRIVATE KEY-----\n]');

$assertion .= base64_encode('
  "iss":"'.$this->configs['client_id'].'",
  "scope":"https://www.googleapis.com/auth/youtube",   
  "aud":"'.$url.'",
  "exp":'.($time+3600).',
  "iat":'.$time.'
');
$assertion .= '.'.base64url_encode(sign($assertion, '[-----BEGIN PRIVATE KEY-----privateKeyHere-----END PRIVATE KEY-----\n]'));

【讨论】:

【参考方案2】:

您收到 invalid_grant 是因为您这样做了

1.

'grant_type' => urlencode('urn:ietf:params:oauth:grant-type:jwt-bearer')

把它改成只是

'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer'

2.

iss 断言字段不是来自凭据的 ClientID。这是电子邮件地址。

3.

时间应该是UTC,所以你应该这样做

$time = gmmktime();

4.

如@florent-morselli 所述,您需要签名。这是真的。

5.

检查您机器的时间。时间同步很重要

之后一切都会好起来的,Google 会给你一个 access_token。

顺便说一句:

  privateKey should be without []\n characters (In my case this is lines of 64 symbols)

  CURLOPT_POST takes only true or false

  http_build_query is better way to build queryString

  'Host: accounts.google.com' in $headers is useless, because you set CURLOPT_URL

【讨论】:

以上是关于Google API OAuth 2.0 身份验证返回“invalid_grant” - 可能的私钥签名格式问题?的主要内容,如果未能解决你的问题,请参考以下文章

带有 Google Analytics API v3 的 OAuth 2.0

通过 OAuth 2.0 自动使用 google-api-dotnet-client

使用 Laravel API 实现 OAuth 2.0 身份验证

从 Google OAuth 2.0 PHP API 获取用户信息

HTTP 基本身份验证和 OAuth 2.0 相同吗?

在弹出窗口中打开 Google SignIn 用户身份验证,需要使用 OAuth 2.0 在同一选项卡本身中打开