记录一下之前上架苹果应用商店遇到的坑。因为App使用微信登录没有接入苹果登陆,被官方审核驳回。苹果登录是 iOS13 新增加的功能,当你的应用使用了第三方登录比如微信登录,同时也需要集成苹果登录,否则提交AppStore审核会被拒绝。 详情参考:App Store 审核指南 - 通过 Apple 登录
一、在 HBuilderX 配置 apple 登录
在项目根目录找到manifest.json
文件,点击即可跳转配置页。选择App模块配置,将苹果登陆勾选上。
二、使用苹果登录首先需要在苹果开发者后台开启 App 的 Sign In with Apple 服务。
三、代码集成
在页面中放置苹果登陆按钮组件。这里苹果对于这个按钮是有要求的。我这里使用苹果官方推荐的圆形按钮。官方提供的基本满足使用条件了。如有特殊需求按官方要求设计按钮即可。如下所示,这里因为我的模拟器没有安装微信和QQ,所以不会出现按钮。
然后这里也要注意一个问题,因为苹果授权登录(Sign in with Apple)是 iOS 13 才有的,所以要调用 uni.getSystemInfo()
获取系统信息做下系统版本判断。
这里我是用的是5+App 的api 你也可以使用uniapp 封装的api。uni.login()
//#ifdef APP-PLUS
appleLogin() {
var appleOauth = null;
plus.oauth.getServices(function(services) {
for (var i in services) {
var service = services[i];
// 获取苹果授权登录对象,苹果授权登录id 为 'apple' iOS13以下系统,不会返回苹果登录对应的 service
if (service.id == 'apple') {
appleOauth = service;
break;
}
}
if (!appleOauth) {
plus.nativeUI.toast('暂不支持apple账户登陆')
return
}
appleOauth.login(function(oauth) {
// 授权成功,苹果授权返回的信息在 oauth.target.appleInfo 中
plus.nativeUI.showWaiting('登陆中...')
//向后台发送登陆需要的参数
}, function(err) {
// 授权失败 error
console.log(err);
plus.nativeUI.toast('授权登陆失败')
}, {
// 默认只会请求用户名字信息,如需请求用户邮箱信息,需要设置 scope: 'email'
scope: 'email'
})
}, function(err) {
console.log(err);
// 获取 services 失败
})
},
//#endif
uni.login()
授权成功回调示例
{
"appleInfo": "getUserInfo:ok",
"rawData": "json字符串",
"userInfo": {
"openId": "xxx.xxxxx.xxx", // 苹果用户唯一标识符,该值在同一个开发者账号下的所有 App 下是一样的,开发者可以用该唯一标识符与自己后台系统的账号体系绑定起来。
"fullName": {}, // 当且仅当第一次授权才会返回
"authorizationCode": "12345678xxx", // 服务器验证需要使用的参数
"identityToken": "header.payload.signature", // 服务器验证需要使用的参数
"realUserStatus": 1 // 用于判断当前登录的苹果账号是否是一个真实用户
},
"signature": ""
}
5+App 的授权成功回调示例
{
"target": {
"id": "apple",
"description": "Apple",
"authResult": {
"access_token": "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmFsaGVscC53b3JkQXBwIiwiZXhwIjoxNTk2MzA4NzQwLCJpYXQiOjE1OTYzMDgxNDAsInN1YiI6IjAwMTM3My5jZDg4NmJkNWRlZWY0MjI3OGViOTg3ZDgwYTY3YjRlNi4wNDM4IiwiY19oYXNoIjoiX1Z1UzBOZ0RFY1BNanFQRmVWRUJoQSIsImVtYWlsIjoiOTlpd2szZG5iMkBwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSIsImF1dGhfdGltZSI6MTU5NjMwODE0MCwibm9uY2Vfc3VwcG9ydGVkIjp0cnVlfQ.EW6ANPxkiHCQPz297Q_0ZhCQvOJ1tpnQGYG2w82kIwQRiQOZydYzXg2pHDf20ITpGwwSnuXRbuCW4PkpUPunTf8VdM6N9qPVuTszvTQ9C6awwSdt9BoeNrkztaiHTxdzpJOHZyeHFlFRIbTxpqggdFqwLR361F475aN1McdOVsZriOnRdaL7PBd3Ua5di0I2NqAQBn94MCOzX3rWHMpDx2MhuDePMsJo_a_tzWQS6M_4PvtJbP0fa-p8s4Hjezk9uoimIwOD-Qdzg4HdLMwGJzzmU3qEoV6g4nJrcWnaGoywsrXVO85r81v59d4To079OMUJUYcf2LUv0ur0DWO91Q",
"openid": "001373.cd886bd5deef42278eb987d80a67b4e6.0438" // 苹果用户唯一标识符,该值在同一个开发者账号下的所有 App 下是一样的,开发者可以用该唯一标识符与自己后台系统的账号体系绑定起来。
},
"userInfo": {
"openid": "001373.cd886bd5deef42278eb987d80a67b4e6.0438"
},
"appleInfo": {
"authorizationCode": "c2d2cfbe49e174d5c914ce07c3e2c0119.0.nrtxt.DhS_Ovj6R_LVtM8qvC4yww",
"fullName": {},
"identityToken": "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmFsaGVscC53b3JkQXBwIiwiZXhwIjoxNTk2MzA4NzQwLCJpYXQiOjE1OTYzMDgxNDAsInN1YiI6IjAwMTM3My5jZDg4NmJkNWRlZWY0MjI3OGViOTg3ZDgwYTY3YjRlNi4wNDM4IiwiY19oYXNoIjoiX1Z1UzBOZ0RFY1BNanFQRmVWRUJoQSIsImVtYWlsIjoiOTlpd2szZG5iMkBwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSIsImF1dGhfdGltZSI6MTU5NjMwODE0MCwibm9uY2Vfc3VwcG9ydGVkIjp0cnVlfQ.EW6ANPxkiHCQPz297Q_0ZhCQvOJ1tpnQGYG2w82kIwQRiQOZydYzXg2pHDf20ITpGwwSnuXRbuCW4PkpUPunTf8VdM6N9qPVuTszvTQ9C6awwSdt9BoeNrkztaiHTxdzpJOHZyeHFlFRIbTxpqggdFqwLR361F475aN1McdOVsZriOnRdaL7PBd3Ua5di0I2NqAQBn94MCOzX3rWHMpDx2MhuDePMsJo_a_tzWQS6M_4PvtJbP0fa-p8s4Hjezk9uoimIwOD-Qdzg4HdLMwGJzzmU3qEoV6g4nJrcWnaGoywsrXVO85r81v59d4To079OMUJUYcf2LUv0ur0DWO91Q",// 服务器验证需要使用的参数
"realUserStatus": 1,
"user": "001373.cd886bd5deef42278eb987d80a67b4e6.0438"
}
}
}
后端从前端接收到 identityToken 字符串之后,由于 identityToken 是一个 JWT,所以这里需要安装如下第三方库对 identityToken 进行解析。
composer require firebase/php-jwt
接下来还需要获取解密 JWT 的 JWK。可以通过访问 https://appleid.apple.com/auth/keys 得到一个 keys 列表,也就是 JWK 列表。这也就意味着客户端向服务器提交的 identityToken 可能是用 keys 里面的特定某个 JWK 来进行加密的。 获取到keys 后,需要确定到底是使用哪个JWK加密的
$tks = explode('.', $identityToken);
list($headb64, $bodyb64, $cryptob64) = $tks;
$header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64));
$key_used = $keys_map[$header->kid];
确定了 JWK 之后,还需要安装如下第三方库将 JWK 转换为 PEM。
composer require codercat/jwk-to-pem
PHP代码示例
/**
* 苹果登陆
* auth_time: 签名时间
* @param $params
* @return array|bool
*/
public function apple_login($params)
{
try {
$apple_info = $params['target']['appleInfo'];
$identityToken = $apple_info['identityToken'];
// 获取 JWK 列表
$keys_map = $this->get_apple_auth_keys();
if (!$keys_map) {
exception('获取JWK列表');
}
// 定位用于加密当前 identityToken 的 JWK
$tks = explode('.', $identityToken);
list($headb64, $bodyb64, $cryptob64) = $tks;
$header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64));
$key_used = $keys_map[$header->kid];
//获取用户的数据
$jwkConverter = new JWKConverter();
$publicKey = $jwkConverter->toPEM($key_used);
$userinfo = JWT::decode($identityToken, $publicKey, ['RS256']);
if (!$userinfo) {
exception('获取用户信息失败');
}
// $userinfo里的sub 参数即可用于绑定用户信息,相当于weixin 的unionid。这里只有用户第一次授权登陆时能获取到用户的邮箱信息,第二次就获取不到了
}
return $data;
} catch (Exception $e) {
$this->setError($e->getMessage());
return false;
} catch (ExpiredException $e) {
$this->setError($e->getMessage());
return false;
}
}
/**
* Apple公钥
* @return array|bool|mixed
*/
public function get_apple_auth_keys()
{
$keys_map = Redis::instance()->hGetJson('apple_auth', 'apple_auth_keys');
if (!$keys_map) {
$request_url = 'https://appleid.apple.com/auth/keys';
$response = Http::get($request_url);
$resData = json_decode($response, true);
$keys = $resData['keys'];
$keys_map = [];
foreach ($keys as $key) {
$keys_map[$key['kid']] = $key;
}
Redis::instance()->hSetJson('apple_auth', 'apple_auth_keys', $keys_map, 86400 * 30);
}
return $keys_map;
}
获取到的用户信息示例
{
"data": {
"code": 1,
"msg": "操作成功",
"data": {
"iss": "https://appleid.apple.com",//签发机构,苹果
"aud": "com.xxx.xxx",//接收者,目标app,对应Apple开发者账户中的client_id
"exp": 1596308740,//过期时间
"iat": 1596308140,//签发时间
"sub": "001373.cd886bd5deef42278eb987d80a67b4e6.0438",//它是用户的唯一标识符,很重要,和微信unionid 一样
"c_hash": "_VuS0NgDEcPMjqPFeVEBhA",//authorizationCode的hash值,用于验证authorizationCode
"email": "99iwk3dnb2@privaterelay.appleid.com",//用户邮箱
"email_verified": "true",//邮箱是否经过验证
"is_private_email": "true",//是否是加密邮箱
"auth_time": 1596308140,//请求授权的时间
"nonce_supported": true //指示对待nonce的方式, true: 如果授权请求时有给nonce,但返回的token不含nonce,则说明此次请求失败.。false: 不支持nonce,忽略nonce
}
},
"statusCode": 200,
"errMsg": "request:ok"
}
参考文章:https://blog.csdn.net/zou8944/article/details/105167965/
本文为蔡关荣原创文章,转载无需和我联系,但请注明来自蔡关荣博客https://caiguanrong.com
最新评论