APP登录授权完成后,上传userID和identityToken给服务端,服务端进行JWT验证
服务端是用PHP语言处理的,涉及的PHP类库如下:
JWT:https://github.com/lcobucci/jwt.git
JWK转PEM:https://github.com/acodercat/php-jwk-to-pem.git
<?php
use Lcobucci\JWT\Token\Parser;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use CoderCat\JWKToPEM\JWKConverter;
use Lcobucci\JWT\Encoding\JoseEncoder;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
/**
* 校验苹果登陆用户的身份信息
* 不同产品的aud不同,修改aud(Your client_id in your Apple Developer account)即可
* 文档地址:https://developer.apple.com/documentation/signinwithapplerestapi/authenticating_users_with_sign_in_with_apple
*/
class AppleSignIn
{
const APPLE_AUTH_KEY_URL = 'https://appleid.apple.com/auth/keys';
const LOG_DIR = '/data/logs/applein';
private $aud = ''; // Your client_id in your Apple Developer account
public function __construct($aud)
{
$this->aud = $aud;
}
/**
* 检查用户数据
*
* @param string $identityToken
* @param string $openid
* @return boolean
*/
public function checkIdentityToken($identityToken, $openid)
{
try {
$parse = new Parser(new JoseEncoder());
$token = $parse->parse($identityToken);
$kid = $token->headers()->get('kid');
// 验证时间/iss/aud等参数
$iss = $token->claims()->get('iss');
$aud = $token->claims()->get('aud'); //返回的是个数组
$isExpired = $token->isExpired(new DateTimeImmutable()) ? 1 : 0;
if ($isExpired || !in_array($this->aud, $aud) || $iss != 'https://appleid.apple.com') {
$logStr = date('Y-m-d H:i:s') . ' [error] verify params fail [isExpired] ' . $isExpired . ' [aud] ' . (int)(in_array($this->aud, $aud)) . ' [iss] ' . (int)($iss != 'https://appleid.apple.com')
. ' [jwt header] ' . json_encode($token->headers()->all()) . ' [jwt claims] ' . json_encode($token->claims()->all());
$this->writelog($logStr);
return false;
}
$sub = strtolower($token->claims()->get('sub'));
if ($sub != $openid) {
$logStr = date('Y-m-d H:i:s') . ' [error] verify openid fail' . ' [jwt header] ' . json_encode($token->headers()->all()) . ' [jwt claims] ' . json_encode($token->claims()->all()) . ' [openid] ' . $openid . ' [sub] ' . $sub;
$this->writelog($logStr);
return false;
}
$publicKey = $this->getAuthKey($kid);
if (!$publicKey) {
// 公钥获取失败
$logStr = date('Y-m-d H:i:s') . ' [error] public key not find [kid] ' . $kid;
$this->writelog($logStr);
return false;
}
$signer = new Sha256();
$validator = new SignedWith($signer, InMemory::plainText($publicKey));
$validator->assert($token);
$logStr = date('Y-m-d H:i:s') . ' verify success' . ' [jwt header] ' . json_encode($token->headers()->all()) . ' [jwt claims] ' . json_encode($token->claims()->all());
$this->writelog($logStr);
} catch (\Exception $e) {
$logStr = date('Y-m-d H:i:s') . ' [error] system error,error=' . $e->getMessage() . ' [identityToken] ' . $identityToken . ' [openid] ' . $openid;
$this->writelog($logStr);
return false;
}
return true;
}
/**
* 根据KeyId 获取Apple公钥
*
* @param string $keyId
* @return void
*/
private function getAuthKey($keyId): string
{
$request = Utils::curl(self::APPLE_AUTH_KEY_URL, [], [CURLOPT_POST => false, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_TIMEOUT => 60]);
if (!$request) {
return '';
}
$request = json_decode($request, true);
$keysArr = $request['keys'];
$keysArr = array_column($keysArr, null, 'kid');
if (!isset($keysArr[$keyId])) {
return '';
}
$keyArr = $keysArr[$keyId];
$publicKey = $this->build($keyArr);
if (!$publicKey) {
return '';
}
return $publicKey;
}
/**
* 根据JWK构建PEM格式的RSA公钥
*
* @param array $jwk
* @return string
*/
private function build($jwk)
{
$jwkConverter = new JWKConverter();
$publicKey = $jwkConverter->toPEM($jwk);
return $publicKey;
}
/**
* 记录日志
*
* @param string $logStr
* @return boolean
*/
private function writelog($logStr)
{
//var_dump($logStr);
if (!is_dir(self::LOG_DIR)) {
@mkdir(self::LOG_DIR, 0777);
}
if (is_dir(self::LOG_DIR)) {
$logFile = self::LOG_DIR . '/apple_sign_in_' . date('Ymd') . '.log';
file_put_contents($logFile, $logStr . PHP_EOL, FILE_APPEND);
return true;
}
return false;
}
}
文章评论