kevinxie博客

分享快乐,享受阳光!

  • 首页
  • 游戏
  • 生活
  • 联系方式
  • 首页
  • 游戏
  • 生活
  • 联系方式

在备用侧边栏没有发现任何小工具!

在备用侧边栏没有发现任何小工具!

PHP

APPLE游客验证登录代码

2020年7月14日 /

代码1: lib.applelogin.php

<?php
/**
 * apple登陆
 *
 * @author kevinxie
 * @date 2020/04/30
 */
class Lib_Applelogin
{
	protected static $_instance = array();
	
	public function __construct()
	{
		
	}
	/**
	 * 对像生产器
	 *
	 * @return object
	 */
	public static function factory() 
	{
		if ( !isset( self::$_instance[__METHOD__] ) || !is_object( self::$_instance[__METHOD__] ) ) 
		{
			self::$_instance[__METHOD__] = new self();
		}
		return self::$_instance[__METHOD__];
	}

    /**
     * Apple登陆验证
     * 
     * @param $clientUser 验证的用户id
     * @param $identityToken 验证token
     * @return array
     */
    public static function appleAuth( $clientUser, $identityToken)
    {
        //使用Apple标识令牌解析提供的登录,返回一个对象
        $appleSignInPayload = Lib_Applelogin::factory()->getAppleSignInPayload($identityToken);

        /**
         * 确定客户端提供的用户是否有效
         */
        $isValid = $appleSignInPayload->verifyUser($clientUser);

        /**
         * 使用Apple电子邮件和用户凭据获取登录
         */
        $email = $appleSignInPayload->getEmail();
        $userInfo['email'] = $email;

        if ( $isValid) 
        {
            $ret["errorCode"] = 0;
            $ret["errorMsg"]  = "success";
            $ret["data"]      = $userInfo;
        }
        else
        { //无效用户
            $ret["errorCode"] = -1;
            $ret["errorMsg"]  = "fail";
            $ret["data"]      = '';
        }

        return $ret;
    }
    
    /**
     * 使用Apple标识令牌解析提供的登录
     *
     * @param string $identityToken
     * @return array
     */
    public static function getAppleSignInPayload(string $identityToken)
    {
        $identityPayload = Lib_Applelogin::factory()->decodeIdentityToken($identityToken);
        return Lib_Aspayload::factory($identityPayload);
    }

    /**
     * 使用Apple的公钥对Apple编码的JWT进行解码以进行签名
     *
     * @param string $identityToken
     * @return object
     */
    public static function decodeIdentityToken(string $identityToken)
    {
        //解码token获取kid
        $publicKeyKid = Lib_Jwt::factory()->getPublicKeyKid($identityToken);

        $publicKeyData = Lib_Applelogin::factory()->fetchPublicKey($publicKeyKid);

        $publicKey = $publicKeyData['publicKey'];
        $alg = $publicKeyData['alg'];

        $payload = Lib_Jwt::factory()->decode($identityToken, $publicKey, [$alg]);

        return $payload;
    }

    /**
     * 获取苹果的公钥,用于解码JWT中的登录。
     *
     * @param string $publicKeyKid
     * @return array
     */
    public static function fetchPublicKey(string $publicKeyKid)
    {
        //获取苹果公钥,有多个
        $decodedPublicKeys = Lib_Functions::factory()->curl('https://appleid.apple.com/auth/keys');
        if(!isset($decodedPublicKeys['keys']) || count($decodedPublicKeys['keys']) < 1) 
        {
            die('Invalid key format.');
        }

        $kids = array_column($decodedPublicKeys['keys'], 'kid');

        //获取对应的公钥
        $parsedKeyData = $decodedPublicKeys['keys'][array_search($publicKeyKid, $kids)];

        //创建证书,并从证书中解析出密钥
        $parsedPublicKey= Lib_Jwt::factory()->parseKey($parsedKeyData);

        //返回包含密钥详情的数组
        $publicKeyDetails = openssl_pkey_get_details($parsedPublicKey);

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

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

}


?>

代码2: lib.aspayload.php

<?php
/**
 * 一个类装饰器,用于从客户端解码已签名的JWT
 */

class Lib_Aspayload 
{
	protected static $_instance = array();
	protected $_instance2;
	
	public function __construct( $instance) 
	{
        if(is_null($instance)) 
        {
            die('ASPayload received null instance.');
        }
        $this->_instance2 = $instance;
    }
	/**
	 * 对像生产器
	 *
	 * @return object
	 */
	public static function factory( $param) 
	{
		if ( !isset( self::$_instance[__METHOD__] ) || !is_object( self::$_instance[__METHOD__] ) ) 
		{
			self::$_instance[__METHOD__] = new self( $param);
		}
		return self::$_instance[__METHOD__];
	}

    public function __call($method, $args) {
        return call_user_func_array(array($this->_instance2, $method), $args);
    }

    public function __get($key) {
        return (isset($this->_instance2->$key)) ? $this->_instance2->$key : null;
    }

    public function __set($key, $val) {
        return $this->_instance2->$key = $val;
    }

    public function getEmail() {
        return (isset($this->_instance2->email)) ? $this->_instance2->email : null;
    }

    public function getUser() {
        return (isset($this->_instance2->sub)) ? $this->_instance2->sub : null;
    }

    public function verifyUser(string $user) {
        return $user === $this->getUser();
    }
}
?>

代码3: lib.jwt.php

<?php
/**
 * 基于此规范的JSON Web令牌实现
 * https://tools.ietf.org/html/rfc7519
 *
 */
class Lib_Jwt
{

    /**
     * The server leeway time in seconds, to aware the acceptable different time between clocks
     * of token issued server and relying parties.
     * When checking nbf, iat or expiration times, we want to provide some extra leeway time to
     * account for clock skew.
     */
    public static $leeway = 0;

    /**
     * Allow the current timestamp to be specified.
     * Useful for fixing a value within unit testing.
     *
     * Will default to PHP time() value if null.
     */
    public static $timestamp = null;

    public static $supported_algs = array(
        'HS256' => array('hash_hmac', 'SHA256'),
        'HS512' => array('hash_hmac', 'SHA512'),
        'HS384' => array('hash_hmac', 'SHA384'),
        'RS256' => array('openssl', 'SHA256'),
        'RS384' => array('openssl', 'SHA384'),
        'RS512' => array('openssl', 'SHA512'),
    );
	protected static $_instance = array();
	
	public function __construct()
	{
		
	}
	/**
	 * 对像生产器
	 *
	 * @return object
	 */
	public static function factory() 
	{
		if ( !isset( self::$_instance[__METHOD__] ) || !is_object( self::$_instance[__METHOD__] ) ) 
		{
			self::$_instance[__METHOD__] = new self();
		}
		return self::$_instance[__METHOD__];
	}
    /**
     * 将JWT字符串解码为PHP对象
     *
     * @param string        $jwt            The JWT
     * @param string|array  $key            The key, or map of keys.
     *                                      If the algorithm used is asymmetric, this is the public key
     * @param array         $allowed_algs   List of supported verification algorithms
     *                                      Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
     *
     * @return object The JWT's payload as a PHP object
     *
     * @uses jsonDecode
     * @uses urlsafeB64Decode
     */
    public static function decode($jwt, $key, array $allowed_algs = array())
    {
        $timestamp = is_null(static::$timestamp) ? time() : static::$timestamp;

        if (empty($key)) {
            die('Key may not be empty');
        }
        $tks = explode('.', $jwt);
        if (count($tks) != 3) {
            die('Wrong number of segments');
        }
        list($headb64, $bodyb64, $cryptob64) = $tks;
        if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) {
            die('Invalid header encoding');
        }
        if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) {
            die('Invalid claims encoding');
        }
        if (false === ($sig = static::urlsafeB64Decode($cryptob64))) {
            die('Invalid signature encoding');
        }
        if (empty($header->alg)) {
            die('Empty algorithm');
        }
        if (empty(static::$supported_algs[$header->alg])) {
            die('Algorithm not supported');
        }
        if (!in_array($header->alg, $allowed_algs)) {
            die('Algorithm not allowed');
        }
        if (is_array($key) || $key instanceof \ArrayAccess) {
            if (isset($header->kid)) {
                if (!isset($key[$header->kid])) {
                    die('"kid" invalid, unable to lookup correct key');
                }
                $key = $key[$header->kid];
            } else {
                die('"kid" empty, unable to lookup correct key');
            }
        }

        // 检查签名
        if (!static::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
            die('Signature verification failed');
        }

        // 检查是否定义了nbf。这是令牌可以是实际使用的时间。如果还没到那个时候,就中止
        if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) {
            die('Cannot handle token prior');
        }

        // 检查此令牌是否已在“now”之前创建。这将防止使用为以后使用而创建的令牌(并且没有正确使用nbf声明).
        if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) {
            die('Cannot handle token prior');
        }

        // 检查此令牌是否已过期.
        // if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
        //    die('Expired token');
        //}

        return $payload;
    }

    /**
     * URL安全Base64解码字符串,并转换成PHP对象
     * @param string $jwt
     * @return mixed
     */
    public static function getPublicKeyKid(string $jwt)
    {
        $tks = explode('.', $jwt);
        if (count($tks) != 3) {
            die('Wrong number of segments');
        }
        list($headb64, $bodyb64, $cryptob64) = $tks;
        if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) {
            die('Invalid header encoding');
        }
        return $header->kid;
    }

    /**
     * 将PHP对象或数组转换并签名为JWT字符串。
     *
     * @param object|array  $payload    PHP object or array
     * @param string        $key        The secret key.
     *                                  If the algorithm used is asymmetric, this is the private key
     * @param string        $alg        The signing algorithm.
     *                                  Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
     * @param mixed         $keyId
     * @param array         $head       An array with header elements to attach
     *
     * @return string A signed JWT
     *
     * @uses jsonEncode
     * @uses urlsafeB64Encode
     */
    public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null)
    {
        $header = array('typ' => 'JWT', 'alg' => $alg);
        if ($keyId !== null) {
            $header['kid'] = $keyId;
        }
        if ( isset($head) && is_array($head) ) {
            $header = array_merge($head, $header);
        }
        $segments = array();
        $segments[] = static::urlsafeB64Encode(static::jsonEncode($header));
        $segments[] = static::urlsafeB64Encode(static::jsonEncode($payload));
        $signing_input = implode('.', $segments);

        $signature = static::sign($signing_input, $key, $alg);
        $segments[] = static::urlsafeB64Encode($signature);

        return implode('.', $segments);
    }

    /**
     * 用给定的密钥和算法对字符串签名.
     *
     * @param string            $msg    The message to sign
     * @param string|resource   $key    The secret key
     * @param string            $alg    The signing algorithm.
     *                                  Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
     *
     * @return string An encrypted message
     */
    public static function sign($msg, $key, $alg = 'HS256')
    {
        if (empty(static::$supported_algs[$alg])) {
            die('Algorithm not supported');
        }
        list($function, $algorithm) = static::$supported_algs[$alg];
        switch($function) {
            case 'hash_hmac':
                return hash_hmac($algorithm, $msg, $key, true);
            case 'openssl':
                $signature = '';
                $success = openssl_sign($msg, $signature, $key, $algorithm);
                if (!$success) {
                    die("OpenSSL unable to sign data");
                } else {
                    return $signature;
                }
        }
    }

    /**
     * Verify a signature with the message, key and method. Not all methods
     * are symmetric, so we must have a separate verify and sign method.
     *
     * @param string            $msg        The original message (header and body)
     * @param string            $signature  The original signature
     * @param string|resource   $key        For HS*, a string key works. for RS*, must be a resource of an openssl public key
     * @param string            $alg        The algorithm
     *
     * @return bool
     *
     * @throws DomainException Invalid Algorithm or OpenSSL failure
     */
    private static function verify($msg, $signature, $key, $alg)
    {
        if (empty(static::$supported_algs[$alg])) {
            die('Algorithm not supported');
        }

        list($function, $algorithm) = static::$supported_algs[$alg];
        switch($function) {
            case 'openssl':
                $success = openssl_verify($msg, $signature, $key, $algorithm);
                if ($success === 1) {
                    return true;
                } elseif ($success === 0) {
                    return false;
                }
                // returns 1 on success, 0 on failure, -1 on error.
                die(
                    'OpenSSL error: ' . openssl_error_string()
                );
            case 'hash_hmac':
            default:
                $hash = hash_hmac($algorithm, $msg, $key, true);
                if (function_exists('hash_equals')) {
                    return hash_equals($signature, $hash);
                }
                $len = min(static::safeStrlen($signature), static::safeStrlen($hash));

                $status = 0;
                for ($i = 0; $i < $len; $i++) {
                    $status |= (ord($signature[$i]) ^ ord($hash[$i]));
                }
                $status |= (static::safeStrlen($signature) ^ static::safeStrlen($hash));

                return ($status === 0);
        }
    }

    /**
     * 将JSON字符串解码为PHP对象。
     *
     * @param string $input JSON string
     *
     * @return object Object representation of JSON string
     *
     * @throws DomainException Provided string was invalid JSON
     */
    public static function jsonDecode($input)
    {
        if (version_compare(PHP_VERSION, '5.4.0', '>=') && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) {
            /** 在PHP>=5.4.0中,json_decode()接受一个options参数,该参数允许您指定将大型int(如Steam事务id)视为字符串,而不是将它们转换为浮点数的PHP默认行为
             */
            $obj = json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
        } else {
            /**并非所有服务器都支持这一点,因此对于较旧的版本,我们必须在解码之前手动检测JSON字符串中的大整数并引用它们(从而将它们转换为字符串),因此preg_replace()调用.
             */
            $max_int_length = strlen((string) PHP_INT_MAX) - 1;
            $json_without_bigints = preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $input);
            $obj = json_decode($json_without_bigints);
        }

        if (function_exists('json_last_error') && $errno = json_last_error()) {
            static::handleJsonError($errno);
        } elseif ($obj === null && $input !== 'null') {
            die('Null result with non-null input');
        }
        return $obj;
    }

    /**
     * 将PHP对象编码为JSON字符串.
     *
     * @param object|array $input A PHP object or array
     *
     * @return string JSON representation of the PHP object or array
     *
     * @throws DomainException Provided object could not be encoded to valid JSON
     */
    public static function jsonEncode($input)
    {
        $json = json_encode($input);
        if (function_exists('json_last_error') && $errno = json_last_error()) {
            static::handleJsonError($errno);
        } elseif ($json === 'null' && $input !== null) {
            die('Null result with non-null input');
        }
        return $json;
    }

    /**
     * 使用URL安全Base64解码字符串.
     *
     * @param string $input A Base64 encoded string
     *
     * @return string A decoded string
     */
    public static function urlsafeB64Decode($input)
    {
        $remainder = strlen($input) % 4;
        if ($remainder) {
            $padlen = 4 - $remainder;
            $input .= str_repeat('=', $padlen);
        }
        return base64_decode(strtr($input, '-_', '+/'));
    }

    /**
     * 使用URL安全Base64编码字符串
     *
     * @param string $input The string you want encoded
     *
     * @return string The base64 encode of what you passed in
     */
    public static function urlsafeB64Encode($input)
    {
        return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
    }

    /**
     * 创建JSON错误的Helper方法.
     *
     * @param int $errno An error number from json_last_error()
     *
     * @return void
     */
    private static function handleJsonError($errno)
    {
        $messages = array(
            JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
            JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
            JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
            JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
            JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3
        );
        die(
            isset($messages[$errno])
            ? $messages[$errno]
            : 'Unknown JSON error: ' . $errno
        );
    }

    /**
     * 获取加密字符串中的字节数.
     *
     * @param string
     *
     * @return int
     */
    private static function safeStrlen($str)
    {
        if (function_exists('mb_strlen')) {
            return mb_strlen($str, '8bit');
        }
        return strlen($str);
    }

    /**
     * 分析一组JWK键
     * @param $source
     * @return array an associative array represents the set of keys
     */
    public static function parseKeySet($source)
    {
        $keys = [];
        if (is_string($source)) {
            $source = json_decode($source, true);
        } else if (is_object($source)) {
            if (property_exists($source, 'keys'))
                $source = (array)$source;
            else
                $source = [$source];
        }

        if (is_array($source)) {
            if (isset($source['keys']))
                $source = $source['keys'];

            foreach ($source as $k => $v) {
                if (!is_string($k)) {
                    if (is_array($v) && isset($v['kid']))
                        $k = $v['kid'];
                    elseif (is_object($v) && property_exists($v, 'kid'))
                        $k = $v->{'kid'};
                }
                try {
                    $v = self::parseKey($v);
                    $keys[$k] = $v;
                } catch (Exception $e) {

                }
            }
        }
        if (0 < count($keys)) {
            return $keys;
        }
        die('Failed to parse JWK');
    }

    /**
     * 解析JWK键
     * @param $source
     * @return resource|array an associative array represents the key
     */
    public static function parseKey($source)
    {
        if (!is_array($source))
            $source = (array)$source;
        if (!empty($source) && isset($source['kty']) && isset($source['n']) && isset($source['e'])) {
            switch ($source['kty']) {
                case 'RSA':
                    if (array_key_exists('d', $source))
                        die('Failed to parse JWK: RSA private key is not supported');

                    //创建证书
                    $pem = self::createPemFromModulusAndExponent($source['n'], $source['e']);
                    //从证书中提取公钥
                    $pKey = openssl_pkey_get_public($pem);
                    if ($pKey !== false)
                        return $pKey;
                    break;
                default:
                    //Currently only RSA is supported
                    break;
            }
        }

        die('Failed to parse JWK');
    }

    /**
     *
     * 从RSA模和指数信息创建以PEM格式表示的公钥
     *
     * @param string $n the RSA modulus encoded in Base64
     * @param string $e the RSA exponent encoded in Base64
     * @return string the RSA public key represented in PEM format
     */
    private static function createPemFromModulusAndExponent($n, $e)
    {
        $modulus = self::urlsafeB64Decode($n);
        $publicExponent = self::urlsafeB64Decode($e);


        $components = array(
            'modulus' => pack('Ca*a*', 2, self::encodeLength(strlen($modulus)), $modulus),
            'publicExponent' => pack('Ca*a*', 2, self::encodeLength(strlen($publicExponent)), $publicExponent)
        );

        $RSAPublicKey = pack(
            'Ca*a*a*',
            48,
            self::encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])),
            $components['modulus'],
            $components['publicExponent']
        );


        // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption.
        $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA
        $RSAPublicKey = chr(0) . $RSAPublicKey;
        $RSAPublicKey = chr(3) . self::encodeLength(strlen($RSAPublicKey)) . $RSAPublicKey;

        $RSAPublicKey = pack(
            'Ca*a*',
            48,
            self::encodeLength(strlen($rsaOID . $RSAPublicKey)),
            $rsaOID . $RSAPublicKey
        );

        $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" .
            chunk_split(base64_encode($RSAPublicKey), 64) .
            '-----END PUBLIC KEY-----';

        return $RSAPublicKey;
    }

    /**
     * 对长度进行编码
     *
     * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4.  See
     * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
     *
     * @access private
     * @param int $length
     * @return string
     */
    private static function encodeLength($length)
    {
        if ($length <= 0x7F) {
            return chr($length);
        }

        $temp = ltrim(pack('N', $length), chr(0));
        return pack('Ca*', 0x80 | strlen($temp), $temp);
    }
}

执行代码:

include lib.applelogin.php;
include lib.aspayload.php;
include lib.jwt.php;
//$clientUser 验证的用户id
//$identityToken 验证token</p>
$info = Lib_Applelogin::factory()->appleAuth( $clientUser, $identityToken);

Apple LoginAPPLE游客验证登录苹果游客验证登录苹果登录代码
作者 kevinxie 0评论

您可能也喜欢

Google推送firebase PHP代码

2020年7月11日

GooglePay验证支付成功是否可以发货PHP代码

2020年7月11日

苹果推送PHP代码

2020年7月15日

欢迎来到

欢迎来到kevinxie博客

近期文章

  • GO语言IOS推送 2025年1月20日
  • Linux安装MySQL5.7 2022年3月23日
  • Ueditor编辑器linux安装部署 2020年9月29日
  • 苹果推送PHP代码 2020年7月15日
  • 苹果支付PHP代码 2020年7月14日

文章归档

  • 2025年1月 (1)
  • 2022年3月 (1)
  • 2020年9月 (1)
  • 2020年7月 (6)

日历

2025年6月
一 二 三 四 五 六 日
 1
2345678
9101112131415
16171819202122
23242526272829
30  
« 1月    

联系我们

地址
中国,深圳

电话
1368267xxxx

邮箱
snownight1230@126.com

备案号:粤ICP备15050991号-1
Ashe 主题变换 WP Royal.