苹果授权登录 App 对接 PHP

<?php
// 引入配置文件
require_once CFG_PATH_CFG . '/apple.cfg.php';

// 引入JWT/JWK工具类文件
require_once dirname(__FILE__) . '/AppleSignin/jwt/JWT.php';
require_once dirname(__FILE__) . '/AppleSignin/jwt/JWK.php';

/**
 * 苹果登录相关
 */
class AppleauthLib
{
    // 授权登录配置
    private $config = array();

    // APP内苹果授权登录会提供的参数,由客户端传上来
    private $auth = array();

    public function __construct()
    {
        $this->config = cfgGetAppleInfo();
    }

    /**
     * 授权登录信息赋值
     * @param $info
     */
    public function setAuth($info)
    {
        $this->auth = array(
            'userID' => $info['userID'], // 授权的用户唯一标识
            'email' => $info['email'], // 授权的用户资料
            'fullName' => $info['fullName'], // 授权的用户资料
            'authorizationCode' => $info['authorizationCode'], // 授权code
            'identityToken' => $info['identityToken'], // 授权用户的JWT凭证
        );
    }

    /**
     * 验证用户是否正常授权,避免攻击
     * @param $clientUser
     * @param object $payload 对应 $this->decodeIdentityToken() 返回值
     * @return bool
     */
    public function verifyUser($clientUser, $payload)
    {
        $sub = (isset($payload->sub)) ? $payload->sub : null;
        return $clientUser === $sub;
    }

    /**
     * Decode the Apple encoded JWT using Apple's public key for the signing.
     * @return object
     * @throws Exception
     */
    public function decodeIdentityToken()
    {
        $publicKeyKid = JWT::getPublicKeyKid($this->auth['identityToken']);

        $publicKeyData = $this->fetchPublicKey($publicKeyKid);
        $publicKey = $publicKeyData['publicKey'];
        $alg = $publicKeyData['alg'];

        $payload = JWT::decode($this->auth['identityToken'], $publicKey, array($alg));

        return $payload;
    }

    /**
     * Fetch Apple's public key from the auth/keys REST API to use to decode
     * the Sign In JWT.
     *
     * @param string $publicKeyKid
     * @return array
     * @throws Exception
     */
    private function fetchPublicKey($publicKeyKid)
    {
        $publicKeys = file_get_contents('https://appleid.apple.com/auth/keys');
        $decodedPublicKeys = json_decode($publicKeys, true);

        if (!isset($decodedPublicKeys['keys']) || count($decodedPublicKeys['keys']) < 1) {
            throw new Exception('Invalid key format.');
        }

        $kids = array_column($decodedPublicKeys['keys'], 'kid');
        $parsedKeyData = $decodedPublicKeys['keys'][array_search($publicKeyKid, $kids)];
        $parsedPublicKey = JWK::parseKey($parsedKeyData);
        $publicKeyDetails = openssl_pkey_get_details($parsedPublicKey);

        if (!isset($publicKeyDetails['key'])) {
            throw new Exception('Invalid public key details.');
        }

        return [
            'publicKey' => $publicKeyDetails['key'],
            'alg' => $parsedKeyData['alg']
        ];
    }

    /**
     *
     * https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens
     *
     * @return mixed
     * @throws Exception
     *
     * 正常返回值示例
     * {
     * "access_token":"a0996b16cfb674c0eb0d29194c880455b.0.nsww.5fi5MVC-i3AVNhddrNg7Qw",
     * "token_type":"Bearer",
     * "expires_in":3600,
     * "refresh_token":"r9ee922f1c8b048208037f78cd7dfc91a.0.nsww.KlV2TeFlTr7YDdZ0KtvEQQ",
     * "id_token":"eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLnNreW1pbmcuYXBwbGVsb2dpbmRlbW8iLCJleHAiOjE1NjU2NjU1OTQsImlhdCI6MTU2NTY2NDk5NCwic3ViIjoiMDAwMjY2LmRiZTg2NWIwYWE3MjRlMWM4ODM5MDIwOWI5YzdkNjk1LjAyNTYiLCJhdF9oYXNoIjoiR0ZmODhlX1ptc0pqQ2VkZzJXem85ZyIsImF1dGhfdGltZSI6MTU2NTY2NDk2M30.J6XFWmbr0a1hkJszAKM2wevJF57yZt-MoyZNI9QF76dHfJvAmFO9_RP9-tz4pN4ua3BuSJpUbwzT2xFD_rBjsNWkU-ZhuSAONdAnCtK2Vbc2AYEH9n7lB2PnOE1mX5HwY-dI9dqS9AdU4S_CjzTGnvFqC9H5pt6LVoCF4N9dFfQnh2w7jQrjTic_JvbgJT5m7vLzRx-eRnlxQIifEsHDbudzi3yg7XC9OL9QBiTyHdCQvRdsyRLrewJT6QZmi6kEWrV9E21WPC6qJMsaIfGik44UgPOnNnjdxKPzxUAa-Lo1HAzvHcAX5i047T01ltqvHbtsJEZxAB6okmwco78JQA"
     * }
     */
    public function claims()
    {
        $clientSecret = $this->createClientSecret();

        $response = $this->http('https://appleid.apple.com/auth/token', [
            'grant_type' => 'authorization_code',
            'code' => $this->auth['authorizationCode'], // 授权code是有时效性的,且使用一次即失效
            'client_id' => $this->config['app_bundle_id'],
            'client_secret' => $clientSecret,
        ]);

        if (!isset($response->access_token)) {
            throw new Exception('Error getting an access token: ' . $response->error);
        }

        $claims = explode('.', $response->id_token)[1];
        $claims = json_decode(base64_decode($claims));

        return $claims;
    }

    /**
     * 生成 client_secret
     * @return string
     */
    private function createClientSecret()
    {
        $ts = time();
        $payload = array(
            "iss" => $this->config['team_id'],
            "aud" => "https://appleid.apple.com", // 默认值
            "iat" => $ts,
            "sub" => $this->config['app_bundle_id'], // 被授权的APP ID
            "exp" => $ts + 86400 * 10
        );

        $privateKey = $this->config['privateKeyP8'];
        $secret = JWT::encode($payload, $privateKey, 'ES256', $this->config['key_id']);

        return $secret;
    }

    private function http($url, $params = false)
    {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        if ($params)
            curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Accept: application/json',
            'User-Agent: curl', # Apple requires a user agent header at the token endpoint
        ]);
        $response = curl_exec($ch);
        return json_decode($response);
    }

}

 

测试示例:

<?php

/**
 * 苹果授权登录
 *
 */
class AppleauthService extends BaseService
{

    public function __construct($params = array())
    {
        parent::__construct($params);
    }

    /**
     * 基于JWT的算法验证
     * @param $authInfo
     * @return array
     */
    public function verifyByJwt($authInfo)
    {
        $this->lib['appleauth']->setAuth($authInfo);

        try {
            // 解密
            $payload = $this->lib['appleauth']->decodeIdentityToken();

            // 验证收权是否正确
            $flag = $this->lib['appleauth']->verifyUser($authInfo['userID'], $payload);
            !$flag && fnNo(4250, 'no match');

            return array(
                'userID' => $payload->sub,
                'fullName' => isset($authInfo['fullName']) ? $authInfo['fullName'] : '',
                'email' => $payload->email,
                'datas' => json_encode(objectToArray($payload)),
            );
        } catch (Exception $e) {
            fnWLog(array($e->getMessage()), '', 'apple auth error');
            fnNo(4250, 'error', $e->getMessage());
        }

        return array();
    }


    /**
     * 基于授权码的验证
     * @param $authInfo
     * @return array
     */
    public function verifyByCode($authInfo)
    {
        $this->lib['appleauth']->setAuth($authInfo);

        try {
            // 解密
            $claims = $this->lib['appleauth']->claims();

            return array(
                'userID' => $authInfo['userID'],
                'fullName' => isset($authInfo['fullName']) ? $authInfo['fullName'] : '',
                'email' => isset($authInfo['email']) ? $authInfo['email'] : $claims['email'],
                'datas' => json_encode(objectToArray($claims)),
            );

        } catch (Exception $e) {
            fnWLog(array($e->getMessage()), '', 'apple auth error');
            fnNo(4250, 'error', $e->getMessage());
        }

        return array();
    }
}

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值