如何解决签名验证失败-使用Firebase JWT的Apple登录
我正在尝试生成客户端机密,并使用PHP中的Firebase / PHP-jwt验证苹果机密。
// generate the client secret
payload = array(
"iss" => $teamId,'aud' => 'https://appleid.apple.com','iat' => time(),'exp' => time() + 3600,'sub' => $clientId
);
$keycontent = file_get_contents($uri);
$jwt = JWT::encode($payload,$keycontent,'ES256',$key);
//Decode the jwt token
$decoded = JWT::decode($jwt,$rsa->getPublicKey(),array('ES256'));
从苹果(https://appleid.apple.com/auth/keys)获取公共密钥
在执行代码时,我的签名验证失败。
这就是我获取苹果公钥的方式
$cURLConnection = curl_init();
curl_setopt($cURLConnection,CURLOPT_URL,'https://appleid.apple.com/auth/keys');
curl_setopt($cURLConnection,CURLOPT_RETURNTRANSFER,true);
$publickeys = curl_exec($cURLConnection);
curl_close($cURLConnection);
$jsonArrayResponse = json_decode($publickeys);
foreach ($jsonArrayResponse->keys as $publicKey => $publicValue) {
if ($publicValue->kid == $d_keys->kid) {
$rsa = new RSA();
$rsa->loadKey([
'e' => new BigInteger(base64_decode($publicValue->e),256),'n' => new BigInteger(base64_decode($publicValue->n),256)
]);
$decoded = JWT::decode($clientSecretToken,array('ES256'));
}
}
解决方法
问题在于您正在混淆部分登录流程。您将自己的client_secret
创建与Apple的id_token
验证混淆了。您想做的是这样:
- 从客户端应用程序接收苹果的
id_token
(JWT)和authorization_code
- 解码
id_token
的标头,以便抓取kid
(用于验证签名)
$header_base_64 = explode('.',$id_token)[0];
$kid = (JWT::jsonDecode(JWT::urlsafeB64Decode($header_base_64)))->kid;
- 使用Apple的公钥(
id_token
)和GET https://appleid.apple.com/auth/keys
算法验证RS256
的签名。它们采用JWK格式,因此您应该使用刚从kid
提取的id_token
自己构建密钥。
$public_key = (JWK::parseKeySet($apple_jwk_keys))[$kid];
$parsed_id_token = JWT::decode($id_token,$public_key,['RS256']);
-
如果一切顺利,您现在知道您的用户向您发送了一个有效的Apple id_token,并且可以提取所需的字段,例如
userId
和email
,即{{1 }} -
下一步是将您的
$user_id = $parsed_id_token['sub']
换成authorization_code
,这样您每天最多可以验证一次用户。首先,您要创建refresh_token
,这是一个JWT,其中包含您已经创建的所有字段。然后,使用client_secret
算法使用自己的Key + KeyID(在Apple开发门户网站上创建)对此进行签名。代码与您已经拥有的代码相同:
ES256
- 现在您将
payload = array( "iss" => $teamId,'aud' => 'https://appleid.apple.com','iat' => time(),'exp' => time() + 3600,'sub' => $clientId ); $keycontent = file_get_contents($uri); $client_secret = JWT::encode($payload,$keycontent,'ES256',$key);
发送给Apple。 (请注意,如果authorization_code
是由iOS应用程序生成的,则client_id
是应用程序的标识符。如果来自网络客户端,则需要创建专用的服务ID )
id_token
现在,您可以将此//1. build POST data
$post_data = [
'client_id' => $clientId,'grant_type' => 'authorization_code','code' => $client_authorization_code,'client_secret' => $client_secret
];
//2. create and send request
$ch = curl_init("https://appleid.apple.com/auth/token");
curl_setopt($ch,CURLOPT_HTTP_VERSION,CURL_HTTP_VERSION_2_0);
curl_setopt($ch,CURLOPT_HTTPHEADER,[
'Accept: application/x-www-form-urlencoded','User-Agent: curl',//Apple requires a user agent header at the token endpoint
]);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch,CURLOPT_POST,CURLOPT_POSTFIELDS,http_build_query($post_data));
$curl_response = curl_exec($ch);
curl_close($ch);
//3. extract JSON from Apple token response
$data = json_decode($curl_response,true);
$refresh_token = $data['refresh_token'];
保存在数据库中,用于特定的refresh_token
。这样,您最多可以每24小时验证一次用户的真实性。您要做的就是用userId
而不是refresh_token
重复步骤#6,也更改了grant_type(请记住,这次Apple不会给您一个新的{{ 1}})。
就是这样!您无需验证自己的authorization_code
的签名,就可以创建它!苹果是需要这样做的人,让他们来处理。
以下是使用 HTML、Javascript 和 PHP 登录 Apple 的完整示例。
我使用来自 https://github.com/firebase/php-jwt 的 jQuery 和 PHP-JWT
首先从 Apple 开发者门户创建您的 ID 和密钥。这些资源将帮助您获得这些 https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple https://sarunw.com/posts/sign-in-with-apple-4/
登录分为 2 个阶段,首先客户端单击“使用 Apple 登录”按钮并通过 Apple 进行身份验证。这会向我们的 Javascript 返回两条信息,然后我们可以将这些信息发布到 Apple 的服务器以验证客户端并使用 PHP 获取他们的信息。
在本例中,我们使用 Javascript/PHP 来处理登录过程。来自 Apple 的响应是使用 Javascript/PHP 而不是通过重定向 URL 处理的。重定向 URL 永远不会被调用。
HTML/JS 客户端:
<div id="appleid-signin" data-color="white" data-border="true" data-type="sign in" data-height="40" data-width="200" style="margin-top: 18px; cursor: pointer;"></div>
<script type="text/javascript" src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
<script type="text/javascript">
jQuery(document).ready(function(){
AppleID.auth.init({
clientId : "YOUR.CLIENT.ID",scope : "name email",redirectURI : "YOUR://REDIRECT/URI",usePopup : true
});
});
document.addEventListener("AppleIDSignInOnSuccess",(data) => {
//handle successful response
console.log(data);
var appleToken = data.detail.authorization.id_token ;
var appleCode = data.detail.authorization.code ;
console.log("Token: "+appleToken);
console.log("Code: "+appleCode);
jQuery.ajax({url: "verifyToken.php?authCode="+appleCode+"&idToken="+appleToken,success: function(result){
var appleUser = JSON.parse(result);
console.log(appleUser);
console.log("Customer Email: " + appleUser.email);
}});
});
</script>
以上代码由 HTML Div 组成,其中包含使用 Apple 按钮登录和使用 Apple 托管的 Apple javascript 登录。
我在页面加载后使用 jQuery 调用 AppleID.auth.init 函数,以确保在我们尝试调用其函数之前加载了 Apple 托管的 JS。
在用户成功通过 Apple 身份验证后,来自 Apple 的响应将被处理,我们将其发布到我们的 PHP 脚本中,以便与 Apple 验证信息并检索客户信息。 PHP 从 Apple 返回客户信息,在本例中,它将其写入 Web 浏览器控制台,然后是客户的电子邮件地址。
这是处理这个的 PHP (verifyToken.php)。替换顶部的变量并上传您的私钥(最好是安全的地方)。我已经添加了关于在哪里可以找到可用信息的描述:
<?php
// Requires https://github.com/firebase/php-jwt
// Install with: composer require firebase/php-jwt
$id_token = $_REQUEST['idToken']; // Provided after user completed sign in. In authorisation->id_token
$client_authorization_code = $_REQUEST['authCode']; // Provided after user completed sign in. In authorisation->code
$teamId = "01ABC23D4E" ; // Your Team ID from https://developer.apple.com/account/#/membership/
$clientId = "YOUR.CLIENT.ID" ; // Your sing in with apple identifier from https://developer.apple.com/account/resources/identifiers/list
$privKey = file_get_contents("AppleSignIn_AuthKey.p8"); // Provided by Apple only once after you generate a key at https://developer.apple.com/account/resources/authkeys/list
$keyID = "1A2BCD3EFG" ; // The ID for your key from https://developer.apple.com/account/resources/authkeys/list
require __DIR__ . '/vendor/autoload.php';
use \Firebase\JWT\JWT;
use \Firebase\JWT\JWK;
$apple_jwk_keys = json_decode(file_get_contents("https://appleid.apple.com/auth/keys"),null,512,JSON_OBJECT_AS_ARRAY) ;
$keys = array() ;
foreach($apple_jwk_keys->keys as $key)
$keys[] = (array)$key ;
$jwks = ['keys' => $keys];
$header_base_64 = explode('.',$id_token)[0];
$kid = JWT::jsonDecode(JWT::urlsafeB64Decode($header_base_64));
$kid = $kid->kid;
$public_key = JWK::parseKeySet($jwks);
$public_key = $public_key[$kid];
$payload = array(
"iss" => $teamId,'sub' => $clientId
);
$client_secret = JWT::encode($payload,$privKey,$keyID);
$post_data = [
'client_id' => $clientId,'client_secret' => $client_secret
];
$ch = curl_init("https://appleid.apple.com/auth/token");
curl_setopt($ch,http_build_query($post_data));
$curl_response = curl_exec($ch);
curl_close($ch);
$data = json_decode($curl_response,true);
$refresh_token = $data['refresh_token'];
$claims = explode('.',$data['id_token'])[1];
$claims = json_decode(base64_decode($claims));
echo json_encode($claims);
本PHP使用之前Javascript中Apple返回的信息与Apple验证信息。它将来自 Apple 的信息返回给 Javascript。
它返回的信息如下:
(
[iss] => https://appleid.apple.com
[aud] => YOUR.CLIENT.ID
[exp] => 1614170648
[iat] => 1614084248
[sub] => XXXXX.XXXXX.XXXXX
[at_hash] => XXXXXX
[email] => customers@email.address
[email_verified] => true
[auth_time] => 1614084210
[nonce_supported] => 1
)
该过程已完成,请根据需要使用此信息来创建/登录用户。
如果您在 iOS/macOS 上使用 Apple 登录,那么您可以使用“sub”来查找用户,因为这与此返回的结果相同:
ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential;
NSString *user = appleIDCredential.user;
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。