【杂记】集成苹果账户登录过程的一些糟心事

该文章根据 CC-BY-4.0 协议发表,转载请遵循该协议。
本文地址:https://fenying.net/post/2025/01/04/connect-to-apple-account/

这两天给系统接入了使用微软账户和苹果账户登录的功能,微软账户和标准的 OAuth2.0 + OpenID Connect 协议一致,采用通用的 对接模块即可。至于苹果,一如既往地特立独行——搞了一堆特殊,又不提升任何安全性。

1. OAuth2.0 客户端密钥的生成

无论 Google、Microsoft、Facebook,给出的 Client ID 和 Client Secret 都是直接提供好的两段字符串,直接拿来用就行。 而苹果则不同,它会给你一个 ECDSA 私钥,让你自己签发一个 JWT Token 当 Client Secret 用。

也就是说,你必须为集成苹果账户单独开发一套逻辑,现有的 OAuth2.0 对接模块是无法直接使用的。

当然了,还有一个办法是,直接签发一个稍微长期的 JWT Token,然后把这个 Token 当作 Client Secret 使用。这样做的话,就不用 每次都生成一个新的 Token 了,至少可以每隔一段时间才更新一次。

处理逻辑参考微软的 Client Secret ,它也是有过期时间的。

2. 授权码的获取

通常来说,在 OAuth2.0 的授权码模式下,用户在同意授权后,页面会被重定向到我们指定的 redirect_uri 地址,并且是通过 HTTP GET 方法 请求的,授权码会作为参数附在 redirect_uriquery string 中。

微软提供了一个选择,可以在启动授权连接里,指定 response_modeform_post,这样授权码就会通过 HTTP POST 方法,但默认还是 GET 方法。

苹果就不一样了,它在文档中规定:当传递了任意非空 scope(目前仅支持 emailname),授权码会强制通过 HTTP POST 方法发送,而不是 GET 方法。 这就意味着此前开发的通用的 OAuth2.0 对接模块,需要对苹果账户登录做特殊处理,必须增加一个 POST 请求的处理逻辑。

3. 获取客户的 Apple 账户邮箱地址

由于公司业务需要,必须获取到第三方账户的邮箱地址才能创建账户(但若是绑定到现有账户则不要求)。苹果又闹出了一出戏,它规定:

  • 若用户在授权时,选择了隐藏真实邮箱(Hide My Email),则无法获取到用户的真实邮箱地址,只能获取到一个苹果提供的代理邮箱地址。

    如果向代理邮箱地址发送邮件,邮件会被转发到用户的真实邮箱地址。

  • 如果用户此前已经授权过给我们的应用,那么在用户再次授权时,不会传递邮箱地址。

这有什么影响呢?

第一个问题倒不是重点,可以通过 id_token 里的 is_private_email 字段来判断是否是代理邮箱地址,然后拒绝掉这种邮箱地址。

第二个问题就很糟心了,因为当得到 id_token 后,如果此时系统出现了故障,导致用户信息没有保存成功,那么用户再次尝试授权时,就无法获取到 用户的邮箱地址了。一旦遇到这种情况,该用户就再也无法使用苹果账户登录我们的系统了——除非 TA 自己去苹果账户里取消授权。但这样体验就极差了。

这一点倒也不能怪苹果,因为:

  • 一方面,毕竟这是用户的隐私信息,确实越少传递越好;

  • 另一方面,其实如果授权流程失败,系统本来就应该将未完成的授权记录同时从自己的系统里和第三方系统中解除。

    但是微软账户系统并不提供从应用侧取消授权的接口,只能让用户自己去取消授权。所幸微软每次授权都会传递邮箱地址,所以这个问题不会出现。

只不过这个就对开发者提出了更高的要求,而且很多现有的 OAuth2.0/OIDC 集成模块可能是不会处理这种情况的。

开发者需要自己实现一套强有效的解决方案来彻底解决这个问题。

comments powered by Disqus

翻译: