详细配置参考:https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple
本文主要参考:
APP客户端授权登录功能开发不做讲述,主要记录PHP后端如何验证苹果授权登录。
针对后端验证苹果提供了两种验证方式,一种是基于JWT的算法验证,另外一种是基于授权码的验证,本文使用了JWT验证实现。
首先,APP客户端认证成功后会返回ASAuthorizationAppleIDCredential类型的对象,它的主要属性如下:
其次,拿到这些信息,将user,email,fullName,identityToken等信息直接传递给后端,后端对请求进行验证(此处使用identityToken进行JWT验证):
这三部分由"."分割,其中Header和Payload是经过base64编码的。
{
"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,其值采用了BASE64编码,使用时需要先解码。
然后引入JWT算法,下面是主要逻辑及验证接口:
/**
* 第三方登录
*/
public function appleIdLogin()
{
$openid = $this->params('userID', '');
$verifyToken = $this->params('verifyToken', '');
if(empty($openid) || empty($verifyToken)){
$this->responseJson(1, '参数错误', array(), true);
}
//token校验
$verifyRes = $this->appleJwtVerify($verifyToken);
if(isset($verifyRes['jwtStatus']) && $verifyRes['jwtStatus'] == 'failed'){
$this->responseJson(1, $verifyRes['jwtMsg'], array(), true);
return;
}else{
//进行注册
$res = $this->register('appleID', $openid);
}
}
/**
* AppleID登录JWT校验identityToken
* 解析后的identityToken由三部分组成:Header.Payload.Signature
*
* @param string $identityToken
* @return object
* @throws \Exception
*/
private function appleJwtVerify($identityToken = ''){
//获取Apple公钥访问地址:https://appleid.apple.com/auth/keys
//得到Apple公钥:
//{
// "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"
//}
//通过Apple公钥在线(https://8gwifi.org/jwkconvertfunctions.jsp)得到用于解密的pem公钥字符串
$publickKey = "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlxrwmuYSAsTfn+lUu4go
ZSXBD9ackM9OJuwUVQHmbZo6GW4Fu/auUdN5zI7Y1dEDfgt7m7QXWbHuMD01HLnD
4eRtY+RNwCWdjNfEaY/esUPY3OVMrNDI15Ns13xspWS3q+13kdGv9jHI28P87RvM
pjz/JCpQ5IM44oSyRnYtVJO+320SB8E2Bw92pmrenbp67KRUzTEVfGU4+obP5RZ0
9OxvCr1io4KJvEOjDJuuoClF66AT72WymtoMdwzUmhINjR0XSqK6H0MdWsjw7ysy
d/JhmqX5CAaT9Pgi0J8lU/pcl215oANqjy7Ob+VMhug9eGyxAWVfu/1u6QJKePlE
+wIDAQAB
-----END PUBLIC KEY-----";//pem公钥 【也可以通过将RSA公钥modulus(N)和exponent(E)转换为PEM文件】
$decoded = JWT::decode($identityToken, $publickKey, array('RS256'));
return $decoded;
}
/**
* 第三方登录账号注册
*
* @param string $regType 注册类型
* @param string $openid 账户唯一识别
* @throws \Blim\Exception\Stop
*/
public function register($regType = '', $openid = ''){
if(empty($regType) || empty($openid)){
$this->responseJson(1, '注册失败', array(), true);
}else{
//注册逻辑
}
}
/**
* json输出
* @param $code
* @param string $message
* @param array $exts
* @param bool $hasData
* @throws Stop
*/
public function responseJson($code, $message = '', $exts = array(), $hasData = false)
{
$res = array('code' => $code, 'tips' => $message, 'msg' => $message);
$res['data'] = array();
if (!empty($exts)) {
$res['data'] = $exts;
}
app('response')->withJson($res, 200);
app()->stop();
}