我正在尝试使用此library将Apple登录到Android应用。文档中描述了主要流程:该库在Android端返回授权代码。此授权码必须发送到我的后端,后者再将其发送到Apple服务器,以便取回访问令牌。
如here和here所述,为了获得访问令牌,我们需要向Apple API发送参数列表,授权代码和签名的JWT。特别是,JWT需要使用ES256算法使用.p8私钥进行签名,该私钥必须从Apple开发人员门户生成和下载。 Apple doc
这是我的PHP脚本:
<?php
$authorization_code = $_POST('auth_code');
$privateKey = <<<EOD
-----BEGIN PRIVATE KEY-----
my_private_key_downloaded_from_apple_developer_portal (.p8 format)
-----END PRIVATE KEY-----
EOD;
$kid = 'key_id_of_the_private_key'; //Generated in Apple developer Portal
$iss = 'team_id_of_my_developer_profile';
$client_id = 'identifier_setted_in_developer_portal'; //Generated in Apple developer Portal
$signed_jwt = $this->generateJWT($kid, $iss, $client_id, $privateKey);
$data = [
'client_id' => $client_id,
'client_secret' => $signed_jwt,
'code' => $authorization_code,
'grant_type' => 'authorization_code'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://appleid.apple.com/auth/token');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$serverOutput = curl_exec($ch);
curl_close ($ch);
var_dump($serverOutput);
function generateJWT($kid, $iss, $sub, $key) {
$header = [
'alg' => 'ES256',
'kid' => $kid
];
$body = [
'iss' => $iss,
'iat' => time(),
'exp' => time() + 3600,
'aud' => 'https://appleid.apple.com',
'sub' => $sub
];
$privKey = openssl_pkey_get_private($key);
if (!$privKey) return false;
$payload = $this->encode(json_encode($header)).'.'.$this->encode(json_encode($body));
$signature = '';
$success = openssl_sign($payload, $signature, $privKey, OPENSSL_ALGO_SHA256);
if (!$success) return false;
return $payload.'.'.$this->encode($signature);
}
function encode($data) {
$encoded = strtr(base64_encode($data), '+/', '-_');
return rtrim($encoded, '=');
}
?>
问题是苹果的反应总是:
{"error":"invalid_client"}
阅读here似乎问题可能与openSSL有关,后者生成的签名对Apple不正确(“ OpenSSL的ES256签名结果是DER编码的ASN.1结构(其大小超过64)”。 R || S值)”)。
有没有办法使用openSSL获得正确的签名?
p8格式是openssl_sign和openssl_pkey_get_private函数的正确输入吗?
(我注意到,如果在jwt.io中使用所提供的.p8密钥以计算带符号的jwt,则该密钥将不起作用。)
在openSSL文档中,我读到应该提供一个pem密钥,如何将.p8转换为.pem密钥?
我还尝试了一些PHP库,这些库基本上使用与上述相同的步骤,例如firebase/php-jwt和lcobucci/jwt,但是Apple响应仍然是“无效的客户端”。
预先感谢您的帮助,
编辑
我试图从等式中完全删除openSSL。使用从.p8生成的.pem密钥,我用jwt.io生成了一个签名的JWT。使用此签名的JWT,Apple API可以正确答复。至此,我几乎可以确定这是一个openSSL签名问题。关键问题是如何使用PHP和openSSL获得正确的ES256签名。
参考方案
如here所示,问题实际上出在openSSL生成的签名中。
使用ES256,数字签名是两个无符号整数的串联,分别表示为R和S,这是椭圆曲线(EC)算法的结果。 R的长度|| S为64。
openssl_sign函数生成一个签名,该签名是DER编码的ASN.1结构(大小> 64)。
解决方案是将DER编码的签名转换为R和S值的原始串联。在this library中,存在一个函数“ fromDER”,它执行这样的转换:
/**
* @param string $der
* @param int $partLength
*
* @return string
*/
public static function fromDER(string $der, int $partLength)
{
$hex = unpack('H*', $der)[1];
if ('30' !== mb_substr($hex, 0, 2, '8bit')) { // SEQUENCE
throw new \RuntimeException();
}
if ('81' === mb_substr($hex, 2, 2, '8bit')) { // LENGTH > 128
$hex = mb_substr($hex, 6, null, '8bit');
} else {
$hex = mb_substr($hex, 4, null, '8bit');
}
if ('02' !== mb_substr($hex, 0, 2, '8bit')) { // INTEGER
throw new \RuntimeException();
}
$Rl = hexdec(mb_substr($hex, 2, 2, '8bit'));
$R = self::retrievePositiveInteger(mb_substr($hex, 4, $Rl * 2, '8bit'));
$R = str_pad($R, $partLength, '0', STR_PAD_LEFT);
$hex = mb_substr($hex, 4 + $Rl * 2, null, '8bit');
if ('02' !== mb_substr($hex, 0, 2, '8bit')) { // INTEGER
throw new \RuntimeException();
}
$Sl = hexdec(mb_substr($hex, 2, 2, '8bit'));
$S = self::retrievePositiveInteger(mb_substr($hex, 4, $Sl * 2, '8bit'));
$S = str_pad($S, $partLength, '0', STR_PAD_LEFT);
return pack('H*', $R.$S);
}
/**
* @param string $data
*
* @return string
*/
private static function preparePositiveInteger(string $data)
{
if (mb_substr($data, 0, 2, '8bit') > '7f') {
return '00'.$data;
}
while ('00' === mb_substr($data, 0, 2, '8bit') && mb_substr($data, 2, 2, '8bit') <= '7f') {
$data = mb_substr($data, 2, null, '8bit');
}
return $data;
}
/**
* @param string $data
*
* @return string
*/
private static function retrievePositiveInteger(string $data)
{
while ('00' === mb_substr($data, 0, 2, '8bit') && mb_substr($data, 2, 2, '8bit') > '7f') {
$data = mb_substr($data, 2, null, '8bit');
}
return $data;
}
另一点是,应将.pem密钥提供给open_ssl_sign函数。从从Apple开发人员下载的.p8密钥开始,我已经使用openSSL生成了.pem:
openssl pkcs8 -in AuthKey_KEY_ID.p8 -nocrypt -out AuthKey_KEY_ID.pem
在下面的新的generateJWT函数代码中,该代码使用.pem键和fromDER函数转换由openSSL生成的签名:
function generateJWT($kid, $iss, $sub) {
$header = [
'alg' => 'ES256',
'kid' => $kid
];
$body = [
'iss' => $iss,
'iat' => time(),
'exp' => time() + 3600,
'aud' => 'https://appleid.apple.com',
'sub' => $sub
];
$privKey = openssl_pkey_get_private(file_get_contents('AuthKey_.pem'));
if (!$privKey){
return false;
}
$payload = $this->encode(json_encode($header)).'.'.$this->encode(json_encode($body));
$signature = '';
$success = openssl_sign($payload, $signature, $privKey, OPENSSL_ALGO_SHA256);
if (!$success) return false;
$raw_signature = $this->fromDER($signature, 64);
return $payload.'.'.$this->encode($raw_signature);
}
希望能帮助到你
php-casperjs获取内部文本 - php我正在为casperjs使用php包装器-https://github.com/alwex/php-casperjs我正在网上自动化一些重复的工作,我需要访问一个项目的innerText,但是我尚不清楚如何从casperjs浏览器访问dom。我认为在js中我会var arr = document.querySelector('label.input…
php:是否有充分的理由引用所有数组键/索引? - php我正在遍历别人的代码,他们总是避免转义其数组键。例如:$ row_rsCatalogsItems [名称]代替$ row_rsCatalogsItems ['名称']因此,我不断地对自己接触的所有事物进行微小的更改,以应对这些惰性。但是现在我想知道这样做是否有很多好处。我得到它会在默认为字符串之前检查常量(我在处理常量时会讨厌php中的行为,因为即使未定义,…
phpWord中的粗体,空格和缩进文本 - php我有一些要加粗的文本,与前几段和后几段分开并缩进。我不能让所有三个属性一起工作。这适用于粗体和空格:$section = $phpWord->addSection(); $section->addText( 'Re: Your Application for Post of Security Guard', array(…
PHP数组可以这样做吗? - php可以说;我有一个$ friends数组,其中有2,000个不同的friendID号+我有一个带有10,000 bulletinID号的$ bulletins数组,该$ bulletins数组还将具有另一个值,该ID的用户ID是发布公告条目的用户现在可以获取所有具有与FriendsID数组中的userID匹配的userID的bulletinID号吗?甚至有可能…
哪个更好的做法?从Jquery响应获取HTML - php这只是一个问题,以了解人们如何以及如何做到这一点,但是假设用户向列表中添加了一些内容,完成后,它将运行下面的ajax并更新.user-stream-list$.ajax({ url: "user-stream-list.php", success: function(data){ $(".user-stream-list…