我正在尝试连接到需要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