苹果App主体迁移
苹果App主体迁移-苹果登录
迁移流程图
![](https://hwl-1255548986.cos.ap-guangzhou.myqcloud.com/hwl.me苹果主体迁移 流程图 .png)
迁移前操作
获取苹果转移标识符 transfer_sub 苹果官方文档
客户端提供参数:
- client_id
- team_id
- key_id
- 苹果p8文件
- recipient_team_id
后端操作步骤
- 获取client_secret,有效期可配置
- 获取accessToken,缓存一小时
- 获取转移标识符transfer_sub,并入库
获取client_secret
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
require "jwt" key_file = "AuthKey_xxxxx.p8" team_id = "team_id" client_id = "bundle_id" key_id = "key_id" validity_period = 180 private_key = OpenSSL::PKey::EC.new IO.read key_file token = JWT.encode( { iss: team_id, iat: Time.now.to_i, exp: Time.now.to_i + 86400 * validity_period, aud: "https://appleid.apple.com", sub: client_id }, private_key, "ES256", header_fields= { kid: key_id } ) puts token
|
获取AccessToken
1 2 3
| curl --location --request POST 'http://appleid.apple.com/auth/token?grant_type=client_credentials&scope=user.migration&client_id={bundleId}&client_secret={刚刚上面生成的client_secret}' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-raw ''
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| const getAppleAccessToken = async () => { const key = redisKey.string.appleAccessToken; let accessToken = await redis.get(key); if (!accessToken) { const startTime = new Date().getTime(); const bundleId = 'bundle_id'; const clientSecret = 'clientSecret'; const url = `http://appleid.apple.com/auth/token?grant_type=client_credentials&scope=user.migration&client_id=${bundleId}&client_secret=${clientSecret}`; const options = { method: 'POST', uri: url, followAllRedirects: true, followOriginalHttpMethod: true, headers: { 'Content-Type': 'application/x-www-form-urlencoded', } }; const data = await request(options).catch((err) => { console.log(err); }); const endTime = new Date().getTime(); const result = JSON.parse(data); if (result.access_token) { accessToken = result.access_token; console.log(`新生成accessToken ${JSON.stringify(result)}`, '耗时', endTime - startTime); await redis.set(key, accessToken, 'EX', 3600); } } return accessToken; };
|
获取转移标识符transfer_sub
1 2 3
| curl --location --request POST 'http://appleid.apple.com/auth/usermigrationinfo?sub={用户苹果登录时传的appleUserId}&target={target_id}&client_id={bundleId}&client_secret={{刚刚上面的上面生成的client_secret}}' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --header 'Authorization: Bearer {刚刚上面获取的accesstoken}'
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| // 新加坡请求苹果服务器耗时大概是1000ms const getTransferSub = async (appleUserId, token) => { const startTime = new Date().getTime(); const bundleId = 'bundle_id'; // App的bundle_id const targetId = 'targetId'; // 接收App的团队id,recipient_team_id const clientSecret = 'clientSecret'; const url = `http://appleid.apple.com/auth/usermigrationinfo?sub=${appleUserId}&target=${targetId}&client_id=${bundleId}&client_secret=${clientSecret}`; const options = { method: 'POST', uri: url, followAllRedirects: true, followOriginalHttpMethod: true, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': `Bearer ${token}` } }; const data = await request(options).catch((err) => { console.log('获取TransferSub失败', JSON.stringify(err), err.message, redisKey.string.appleAccessToken); // 有部分用户可能取消了苹果登录的授权,所以会失败 throw new Error('getTransferSubError'); }); const endTime = new Date().getTime(); console.log(`获取TransferSub ${data}`, '耗时', endTime - startTime); const result = JSON.parse(data); if (result.error) { console.log('token过期', result.error, redisKey.string.appleAccessToken); // token过期,删除原来的缓存 await redis.del(redisKey.string.appleAccessToken); throw new Error('token_expires'); } return result.transfer_sub; };
|
通过队列消费任务
我这边最多使用了30个容器同时消费,并没有被苹果因为QPS的原因拒绝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| channel.addSetup(async function(channel) { const prefetch = parseInt(process.env.prefetch) || 1; await channel.assertQueue('apple.login.userId.record', { durable: true }); await channel.prefetch(prefetch); channel.setConfig({ DEAD_LETTER_TTLs: [5, 10, 30], // 重试配置 }); await channel.consumerQueue('apple.login.userId.record', async function(data) { console.log('苹果登录用户转移标识符记录:', JSON.stringify(data.username)); await recordUserAppleTransferSub(data); }); }); const recordUserAppleTransferSub = async (data) => { const appleUserId = data.username; if (appleUserId) { const accessToken = await getAppleAccessToken(); if (accessToken) { let transferSub = await getTransferSubByAppleUserId(appleUserId); if (!transferSub) { transferSub = await getTransferSub(appleUserId, accessToken); await updateTransferSubUserData(appleUserId, transferSub); } else { console.log('该用户已经获取了transferSub', JSON.stringify(transferSub), appleUserId); } } } };
|
迁移后
获取新的appleUserId 苹果官方文档
客户端提供参数:
- client_id
- team_id
- key_id
- 苹果p8文件
后端操作步骤
- 获取client_secret,有效期可配置
- 获取accessToken,缓存一小时
- 通过transfer_sub和token,获取新的AppleUserId,并入库
获取新的client_secret
客户端提供新的参数,使用之前的脚本获取client_secret
获取AccessToken
使用新的client_secret获取AccessToken
获取新的AppleUserId(遍历之前的苹果登录转移标识符表)
1 2 3
| curl --location --request POST 'http://appleid.apple.com/auth/usermigrationinfo?transfer_sub={transfer_sub}&client_id={bundleId}&client_secret={{刚刚上面的上面生成的client_secret}}' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --header 'Authorization: Bearer {刚刚上面获取的accesstoken}'
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const getNewAppleUserId = async (transferSub, token) => { const startTime = new Date().getTime(); const bundleId = 'bundle_id'; const url = `http://appleid.apple.com/auth/usermigrationinfo?transfer_sub=${transferSub}&client_id=${bundleId}&client_secret=${clientSecret}`; const options = { method: 'POST', uri: url, followAllRedirects: true, followOriginalHttpMethod: true, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': `Bearer ${token}` } }; const data = await request(options).catch((err) => { console.log('获取NewAppleUserId失败', JSON.stringify(err), err.message, redisKey.string.appleAccessToken); throw new Error('getNewAppleUserId'); }); const endTime = new Date().getTime(); console.log(`获取NewAppleUserId ${data}`, '耗时', `${endTime - startTime}ms`); const result = JSON.parse(data); if (result.error) { console.log('token过期', result.error, redisKey.string.appleAccessToken); await redis.del(redisKey.string.appleAccessToken); throw new Error('token_expires'); } return result.sub; };
|
将新AppleUserId入库,建立新旧AppleUserId的映射关系
在用户表中新增newAppleUserId字段,建立部分索引
当用户调用苹果登录时,先判断用户客户端是否为新版客户端,如果是则查询
1
| ctx.model.User.findOne({ $or: [{ username: appleUserId }, { newAppleUserId: appleUserId }] })
|
苹果App主题迁移-游客登录
客户端上报deviceId和openId
1
| app.router.post('/apple_device_guest', app.controller.device.openIdAndDeviceId); // 苹果主体迁移临时记录
|
客户端通过deviceId获取openId
1
| app.router.get('/apple_device_guest', app.controller.device.getOpenIdByDeviceId); // 获取openId
|
##新版游客登录
- 判断客户端版本是否是在迁移后的新版
- 是的话先通过IDFA(deviceId)获取openId
- 通过openId找到原来的账号进行登录
- 如果没有IDFA则注册新账号进行登录