一、为什么需要苹果登录
根据苹果审核指南要求,如果 App 使用第三方或社交登录服务 (例如,Facebook 登录、Google 登录、通过 Twitter 登录、通过 LinkedIn 登录、通过 Amazon 登录或微信登录) 来对其进行设置或验证这个 App 的用户主帐户,则该 App 必须同时提供“通过 Apple 登录”作为同等选项。
详情参考:
https://developer.apple.com/cn/app-store/review/guidelines/#sign-in-with-apple
二、验证苹果登录的身份的两种方法
第一种是通过验证JWT(JSON Web Token)的 identityToken,涉及到 JWT 的结构、苹果公钥的获取以及 PHP 的 JWT 库使用。
什么是 JWT:
https://lulublog.cn/p/idikS4
第二种方法是验证 authorizationCode,需要私钥文件、KeyID 和 TeamID,通过苹果接口进行验证并获取用户信息。整个过程包括验证流程、所需参数及错误处理。
2.1、第一种方法:验证 identityToken
APP 端登录成功后,会拿到一个 identityToken 的字段,里面的值是一个 JWT,以.符号分隔,分为header、payload、signature 三部分,类似长这样:
xxxx.yyyy.zzzz
APP 需要把这个 identityToken 传给后端,后端进行验证。JWT 里的 signature 部分是苹果使用私钥对其进行的签名,要验证这个签名,需要先获得苹果的公钥,而公钥可以通过 JWKS(JSON Web Key Set)来转换获得。获取 JWKS 的接口是
https://appleid.apple.com/auth/keys
接口返回的内容如上图,可以看到目前苹果有四个 JWK(后面可能会随时变更,因此不能写死在服务端,必须通过接口获取!)。然后我们需要用到一个名为 php-jwt 的第三方库,使用 composer 命令安装:
composer require firebase/php-jwt
安装完后可以使用这个库来验证 JWT 签名和获取 payload 里的信息了,代码如下:
require_once(__DIR__ . "/vendor/autoload.php");
use Firebase\JWT\JWK;
use Firebase\JWT\JWT;
// APP登录成功后获得的identityToken
$identityToken = 'xxxx.yyyy.zzzz';
// 调用苹果接口获取JWKS
$apiResponse = file_get_contents('https://appleid.apple.com/auth/keys');
$jwkSet = json_decode($apiResponse, true);
if (!is_array($jwkSet)) {
exit("Get JWK failed");
}
// 将JWKS转换为公钥
try {
$pubKeys = JWK::parseKeySet($jwkSet);
} catch (\Exception $e) {
exit("Parse key set failed:" . $e->getMessage());
}
// 使用公钥对JWT进行检验
try {
$payload = JWT::decode($identityToken, $pubKeys, ['RS256']);
} catch (\Exception $e) {
exit("Decode JWT failed:" . $e->getMessage());
}
// 若检验通过,会得到JWT里的payload信息
echo(json_encode($payload));
下图是 payload 里包含的信息例子:
获得 payload 信息后,还需要检查以下字段,确保:
iss 的值等于 https://appleid.apple.com
exp 是 JWT 的过期时间,当前时间不能大于 exp,也就是不能过期
aud 的值等于你的应用的 APP ID
全部通过后,就可以使用 sub 字段(用户的苹果账号 ID)和 email 字段(用户的邮箱地址)完成后面的登录逻辑啦。
2.2、第二种方法:验证 authorizationCode
APP 端登录成功后,除了会拿到 identityToken字段外,还可以拿到一个 authorizationCode 字段,下面介绍验证 authorizationCode 的方法。
验证 authorizationCode 需要有 p8 格式的私钥文件、私钥ID(Key ID)、Team ID 这三样东西,私钥文件可以在苹果开发者后台下载,Key ID 和 Team ID 也同样,见下图:
下载 p8 格式私钥后,使用下面命令转换为 pem 格式:
openssl pkcs8 -nocrypt -in AuthKey.p8 -out AuthKey.pem
最后通过苹果提供的接口来验证:
require_once(__DIR__ . "/vendor/autoload.php");
use Firebase\JWT\JWK;
use Firebase\JWT\JWT;
$clientId = ''; // APP的BUNDLE_ID
$authCode = ''; // APP登录成功后获得的authorizationCode
$teamId = ''; // Team ID
$keyId = ''; // Key ID
$privateKeyFilePath = "/path/to/AuthKey.pem"; // pem格式私钥文件的路径
// 使用私钥来生成client_secret
$privateKey = openssl_pkey_get_private("file://$privateKeyFilePath");
if (!$privateKey) {
exit("Get private key failed");
}
$payload = [
'iss' => $teamId,
'iat' => time(),
'exp' => time() + 86400,
'aud' => 'https://appleid.apple.com',
'sub' => $clientId,
];
$clientSecret = JWT::encode($payload, $privateKey, 'ES256', $keyId);
// 调用苹果接口验证authorizationCode
$postParams = [
'client_id' => $clientId,
'client_secret' => $clientSecret,
'code' => $authCode,
'grant_type' => 'authorization_code',
];
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://appleid.apple.com/auth/token',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($postParams),
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
var_dump($httpCode, $response);
若验证成功,苹果会返回 200 HTTP 状态码,并且有返回一个 id_token 字段,里面是一个包含用户信息的 JWT,base64decode 后再 json_decode,就可以获得用户信息。