26

iOS Sign With Apple实践 - 大伟不是戴维

 4 years ago
source link: https://easeapi.com/blog/blog/88-sign-with-apple.html?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
88-sign-with-apple.jpg

iOS Sign With Apple实践

2019-09-14 11:50:56 / easeapi.com

在iOS 13系统中,Apple要求提供第三方登录的APP也需要支持「Sign With Apple」,本文主要介绍「Sign With Apple」在服务端的校验逻辑。

iOS 13系统中,Apple要求提供第三方登录的APP也需要支持「Sign With Apple」。在WWDC 2019中,Apple详细讲解了「Sign In with Apple」的使用:

Introducing Sign In with Apple

这个视频主要演示了在APP端集成和使用「Sign In with Apple」,包含几个主要步骤:

Apple要求在UI上显示的样式必须符合要求,使用ASAuthorizationAppleIDButton可以快速创建符合要求的Button。

  • 触发登录请求

典型的示例代码:

let provider = ASAuthorizationAppleIDProvider()
let request = provider.createRequest()
request.requestedScopes = [.fullName, .email]//请求的用户信息

let vc = ASAuthorizationController.init(authorizationRequests: [request])
vc.delegate = self
vc.presentationContextProvider = self
vc.performRequests()
  • 实现ASAuthorizationControllerPresentationContextProviding,ASAuthorizationControllerDelegate协议

以上步骤在上述视频中有详细讲解,在这里就不再展开。还有包括获取授权状态,从iCloud KeyChain password中快速获取登录信息等API也都比较简单,不再说明。这篇文章主要说明下服务端处理部分。

在APP中,「Sign In with Apple」操作最终会在ASAuthorizationControllerDelegate的:

func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization)

方法中获取账号登录信息。如果认证成功会返回ASAuthorizationAppleIDCredential类型的对象,它的主要属性如下:

用户唯一ID,在一个开发者账号下的APP获取到的是一样的,类似微信开发API中的openid;

  • identityToken

「JWT」格式的token,用于验证信息合法性。

  • email,fullName:用户邮箱,昵称等信息;
  • realUserStatus:是否是“真实用户”,可用于反作弊,对抗黑灰产;

那么拿到这些信息该如何使用?

你可以将user,email,fullName等信息直接传递给后端,后端建立对应的账号信息之后,返回给APP。但这样有个重要的问题就是不能保证安全性,无法判断请求是否是伪造的。这个时候就要使用identityToken了。

注意:当第一次认证成功之后,将不会再返回email,fullName等信息,可以在设置->Apple ID->密码与安全性->使用您AppleID的App 中删除对应的APP。

identityToken的使用

将identityToken转换为字符串:

let identityTokenString = String(data: identityToken!, encoding: .utf8)

identityTokenString实际上是JWT(JSON Web Token)格式的文件,JWT文件由三部分组成:

  • Header
  • Payload
  • Signature

这三部分由"."分割,其中Header和Payload是经过base64编码的。

Header base64解码之后示例:

{
  "alg": "HS256",   //算法类型
  "typ": "JWT"      //token类型
}

Payload base64解码之后示例:

{
  "iss": "https://appleid.apple.com",//数据签发者
  "aud": "com.easeapi.www",//签发对象
  "exp": 1568090840,//过期时间
  "iat": 1568090240,//签发时间
  "sub": "022409.17avbbaf112941e5a722788e7f3880f4.4565",//用户唯一ID
  "c_hash": "bck7ThP_-cuu0nbwWSQOPQ",
  "auth_time": 1568090240
}

而Signature部分就是对Header及Payload两部分内容按指定算法进行签名,大致逻辑如下:

Signature = signature(base64UrlEncode(Header) + "." + base64UrlEncode(Payload), secretKey)
#signature代表具体的加密算法;
#secretKey为密钥;

具体到identityToken,Apple目前采用的是RS256的非对称加密算法:

  • Apple会使用私钥(也即为上面的secretKey)对Header及Payload加密,获取Signature;
  • 将Header,Payload及Signature信息包装为JWT格式文件,即是identityToken;

那么,我们如何才能验证拿到的identityToken是否合法呢,这就要用到Apple提供的公钥了。公钥获取地址:

https://appleid.apple.com/auth/keys
//返回的是一个JSON,包含公钥信息。
{
  "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"
    }
  ]
}

解析identityToken后,通过公钥解密Signature获得Payload信息,和identityToken中存储的Payload对比即可校验信息是否合法。

解析JWT文件和校验的过程已经有不少开源实现,参考JWT的官网:

jwt.io

提供了不同语言的开源实现,以PHP项目为例,我选用了:

firebase/php-jwt

使用它的RS256的接口:

use Firebase\JWT\JWT;
$jwt = "";//jwt字符串

$publicKey = "-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8kGa1pSjbSYZVebtTRBLxBz5H
4i2p/llLCrEeQhta5kaQu/RnvuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t
0tyazyZ8JXw+KgXTxldMPEL95+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4
ehde/zUxo6UvS7UrBQIDAQAB
-----END PUBLIC KEY-----";//pem公钥

$decoded = JWT::decode($jwt, $publicKey, array('RS256'));
print_r($decoded);

注意到这里使用的是PEM格式的公钥,可以通过将RSA公钥modulus(N)和exponent(E)转换为PEM文件,参考:convert-rsa-public-key-to-pem-format

也可以通过下方页面在线将JWT格式公钥直接转为PEM文件:

JWK to PEM Convertor online

跨平台支持

Apple文档的Generate and validate tokens,实际上并不是作用于APP的,而是针对WEB产品的。通过APP端的接口已经能获取token,过期时间等信息,只需要按照上述方式走校验逻辑即可。

「Sign With Apple」提供的JS版本API,能够跨平台支持「Sign With Apple」。WEB APP需要额外的配置,参考:Configure Sign In with Apple for the web

遗留的问题

虽然上述过程解决了服务端验证identityToken的问题,但有隐患:上述过程仅处理了RS256算法的情况,当苹果修改公钥算法之后,需要进行再适配。保险的做法是通过auth/keys接口返回数据自适应解析。

iOS 13 适配
iOS crash log分析实践
Address Sanitizer的原理和使用
iOS 13中dyld 3的改进和优化
iOS 13 Scene Delegate and multiple windows


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK