關於Sign in with Apple (Apple 登錄) PHP的後端驗證

1. 首先閱讀官網文檔 https://developer.apple.com/documentation/signinwithapplerestapi

重點講解蘋果授權登陸 後端PHP如何驗證

1. identityToken 的驗證:

客戶端會向後端 傳遞 userId  identityToken email 等參數

後端必須要驗證 identityToken 的有效性,合法性;關於爲什麼要驗證 可以閱讀官方文檔:

identityToken  是 JWT算法格式

使用到的Apple公鑰接口:GET  https://appleid.apple.com/auth/keys

返回如下:

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "AIDOPK1",
      "use": "sig",
      "alg": "RS256",
      "n": "lxrwmuYSAsTfn-lUu4goZSXBD9ackM9OJuwUVQHmbZo6GW4Fu_auUdN5zI7Y1dEDfgt7m7QXWbHuMD01HLnD4eRtY-RNwCWdjNfEaY_esUPY3OVMrNDI15Ns13xspWS3q-13kdGv9jHI28P87RvMpjz_JCpQ5IM44oSyRnYtVJO-320SB8E2Bw92pmrenbp67KRUzTEVfGU4-obP5RZ09OxvCr1io4KJvEOjDJuuoClF66AT72WymtoMdwzUmhINjR0XSqK6H0MdWsjw7ysyd_JhmqX5CAaT9Pgi0J8lU_pcl215oANqjy7Ob-VMhug9eGyxAWVfu_1u6QJKePlE-w",
      "e": "AQAB"
    }
  ]
}

kid,爲密鑰id標識,簽名算法採用的是RS256(RSA 256 + SHA 256),kty常量標識使用RSA簽名算法,其公鑰參數爲n和e

需要使用n和e 生產公鑰方法如下:

    /**
     *
     * Create a public key represented in PEM format from RSA modulus and exponent information
     *
     * @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 = JWT::urlsafeB64Decode($n);
        $publicExponent = JWT::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;
    }

--------------------------------------------------------------
$pem = self::createPemFromModulusAndExponent($source['n'], $source['e']);
$pKey = openssl_pkey_get_public($pem);
$publicKeyDetails = openssl_pkey_get_details($pKey);
        return [
            'publicKey' => $publicKeyDetails['key'],
            'alg' => $parsedKeyData['alg']
        ];

得到 $publicKeyDetails['key']  這個公鑰後 就可以使用它來 驗證 identityToken的簽名的合法性了:

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

$payload = JWT::decode($identityToken, $publicKey, [$alg]);

verify("$headb64.$bodyb64", $sig, $key, $header->alg)
-------------------------------------------------------
//verify中核心方法:
$success = openssl_verify($msg, $signature, $key, $algorithm);

 

identityToken:
'eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLkxCTS5Wb2NhbE1hdGUiLCJleHAiOjE1NzIzMjA5MDUsImlhdCI6MTU3MjMyMDMwNSwic3ViIjoiMDAxMzA5LjI4Zjk0NGZkYjlhYjQ4YzliMGUyNTZlMDA5ZTZiOGIwLjA3MjIiLCJjX2hhc2giOiJTSUhWWE1QTjFibEJiN0RSZ2hYWjl3IiwiZW1haWwiOiJncWFjaWJ4eWh1QHByaXZhdGVyZWxheS5hcHBsZWlkLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjoidHJ1ZSIsImlzX3ByaXZhdGVfZW1haWwiOiJ0cnVlIiwiYXV0aF90aW1lIjoxNTcyMzIwMzA1fQ.RAXfYah9_owBatsmNTAMWD8eL1WXWO1quVetx1wMPqnZcr2DbrjHznL-g1LCxksIL0W3XuDMqxuU63ov_CXMVKFtHC86Bv6ab5p5CcIpIOGzRd5qnT7DrPDBV6JL822U09GubCbqZ22PD4m7wIRrggQ_V80GkHVH3M81TSJs6VAbTExjRg6RgFhofDWbMsT0h95b77jvTYbV1JzLRGgB1j6AE_CeMUKyGb3rSh0SGCkUX52IOJ2oCqQpB6dsZWS_90TJhCGyuHzfZHAhU7uRZoGvUVaIS212YhxqUvBozis3ZarEPYzGfCYlFMkLyduOvtPu_TEs_8hmljtNeFZZLw';

解析後如下:

HEADER:
{
  "kid": "AIDOPK1",
  "alg": "RS256"
}

PAYLOAD:

{
  "iss": "https://appleid.apple.com",
  "aud": "com.LBM.VocalMate",
  "exp": 1572320905,
  "iat": 1572320305,
  "sub": "001309.28f944fdb9ab48c9b0e256e009e6b8b0.0722",
  "c_hash": "SIHVXMPN1blBb7DRghXZ9w",
  "email": "[email protected]",
  "email_verified": "true",
  "is_private_email": "true",
  "auth_time": 1572320305
}

完整的驗證identityToken的代碼可參考:https://github.com/GriffinLedingham/php-apple-signin

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章