Google API OAuth 2.0认证返回;invalid_grant”-可能存在私钥签名格式问题


Google API OAuth 2.0 authentication returning "invalid_grant" - possible private key signature format issue?

我正在尝试连接到需要OAuth 2.0身份验证的Google API。第一步需要我生成一个JSON Web令牌(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授权服务器支持的唯一签名算法是使用SHA-256哈希算法的RSA。这在JWT标头的alg字段中表示为RS256。"

"使用从谷歌开发者控制台获得的私钥,使用SHA256withRSA(也称为带有SHA-256哈希函数的RSASSA-PKCS1-V1_5-Sign)对输入的UTF-8表示进行签名。输出将是一个字节数组。"

"然后签名必须是Base64url编码的。"

我直接从Google开发控制台复制了私钥(包括"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"错误,有什么想法吗?

您有两个问题:

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

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

base64url_encode 替换您的base64_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]'));

您收到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。此IS电子邮件地址

3.

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

$time = gmmktime();

4.

正如前面提到的@florent morselli,你需要在签名上签名。这是真的。

5.

检查机器的时间。重要的是时间应该同步

在这之后,一切都会好起来,谷歌会给你一个access_token。

BTW:

  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