微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

签名验证失败-使用Firebase JWT的Apple登录

如何解决签名验证失败-使用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验证混淆了。您想做的是这样:

  1. 从客户端应用程序接收苹果的id_token(JWT)和authorization_code
  2. 解码id_token的标头,以便抓取kid(用于验证签名)
$header_base_64 = explode('.',$id_token)[0];
$kid = (JWT::jsonDecode(JWT::urlsafeB64Decode($header_base_64)))->kid;
  1. 使用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']);
  1. 如果一切顺利,您现在知道您的用户向您发送了一个有效的Apple id_token,并且可以提取所需的字段,例如userIdemail,即{{1 }}

  2. 下一步是将您的$user_id = $parsed_id_token['sub']换成authorization_code,这样您每天最多可以验证一次用户。首先,您要创建refresh_token,这是一个JWT,其中包含您已经创建的所有字段。然后,使用client_secret算法使用自己的Key + KeyID(在Apple开发门户网站上创建)对此进行签名。代码与您已经拥有的代码相同:

ES256
  1. 现在您将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 举报,一经查实,本站将立刻删除。