技能组已发布文章列表,有兴趣的同学能够翻翻前面的文章:

第一篇 | iOS 特色 @property 详细探求

第二篇 | iOS 深入了解 Block 运用及原理

第三篇 | iOS 类别 Category 和扩展 Extension 及相关目标详解

第四篇 | iOS 常用锁 NSLock ,@synchronized 等的底层实现详解

第五篇 | iOS 全面了解 Nullability

第六篇 | iOS Equality 详细探求

第七篇 | iOS 异常 (NSException) 和错误 (NSError) 处理详解

iOS 探求 | 第八篇 Runtime 详细探求

引言

什么是“经过 Apple 登录”功用?

“经过 Apple 登录”功用让咱们能够运用现有的 Apple ID 登录第三方 App 和网站,不只快捷轻松,而且更加私密。


  • 关于“经过 Apple 登录”功用

假如咱们在 App 或网站上看到“经过 Apple 登录”按钮,则阐明能够运用自己的 Apple ID 来设置一个帐户。不需求运用交际媒体帐户,也不用填写表单或别的设定新暗码。

“经过 Apple 登录”功用的设计初衷便是为了尊重个人隐私,让咱们能够掌控自己的个人信息。这个功用能够在 iOS、macOS、Apple tvOS 和 watchOS 上以及任何浏览器中作为原生功用运用。


  • 隐私和安全性

  • 在咱们初次登录时,App 和网站只能要求获取咱们的名字和电子邮件地址来设置帐户。

  • 咱们能够运用“躲藏邮件地址”(Apple 的私密电子邮件中转服务)来创立一个仅有的随机电子邮件地址,并借用这个地址将邮件转发到咱们的个人电子邮件地址。这样一来,不用同享自己的个人电子邮件地址,就能够收到由 App 发送的有用邮件。

  • 在运用 App 和网站时,“经过 Apple 登录”功用既不会盯梢也不会分析咱们的行为特征。Apple 只会保留必要的信息,以保证能够登录和管理自己的帐户。

  • “经过 Apple 登录”功用内建了具有两层认证的安全保护机制。假如咱们在运用 Apple 设备,则能够随时经过面庞 ID 或触控 ID 进行登录和重新认证。


  • 详细操作流程

  • 开发证书及权限设置:

  1. 进入开发者后台,挑选 Certificates, Identifiers & Profiles -> Identifiers,找到要增加 Apple 登录的 Identifier,单击进入修改页面,勾选 Sign In With Apple:

iOS 探究 | 第九篇 Sign in with Apple 及撤销令牌(Revoke Tokens)详细探究

点击 Edit 进入 Apple 登录修改页,挑选 Enable as a primary App ID:

iOS 探究 | 第九篇 Sign in with Apple 及撤销令牌(Revoke Tokens)详细探究

  1. 创立用于生成 client_secret 的私钥(后面会介绍怎么生成 client_secret 及怎么运用):

回到 Certificates, Identifiers & Profiles 页面,挑选 Keys:

iOS 探究 | 第九篇 Sign in with Apple 及撤销令牌(Revoke Tokens)详细探究

点击增加 Keys:

iOS 探究 | 第九篇 Sign in with Apple 及撤销令牌(Revoke Tokens)详细探究

勾选 Sign in with Apple,点击 Configure (上图因为已勾选过,因而变成了 Edit)进入 Configure 修改页:

iOS 探究 | 第九篇 Sign in with Apple 及撤销令牌(Revoke Tokens)详细探究

挑选要增加 Apple 登录的 App ID,并保存。

回到 Register a New Key 页面,点击 Continue

iOS 探究 | 第九篇 Sign in with Apple 及撤销令牌(Revoke Tokens)详细探究

点击 Register,进入下载页面,下载保存私钥(妥善保存):

iOS 探究 | 第九篇 Sign in with Apple 及撤销令牌(Revoke Tokens)详细探究

  1. 项目工程中敞开 Sign In With Apple

打开工程,选中项目的 Target 增加敞开 Sign In With Apple 功用:

iOS 探究 | 第九篇 Sign in with Apple 及撤销令牌(Revoke Tokens)详细探究


iOS 端详细实现

  1. 增加登录按钮(详细实例可参阅:Implementing User Authentication with Sign in with Apple)

在示例应用程序中,LoginViewController 在其视图层次结构中显现一个登录表单和一个运用 Apple 登录按钮 (ASAuthorizationAppleIDButton)。视图控制器还将自己增加为按钮的方针,并在按钮接纳到接触事情时传递要调用的操作。

func setupProviderLoginView() {
    let authorizationButton = ASAuthorizationAppleIDButton()
    authorizationButton.addTarget(self, action: #selector(handleAuthorizationAppleIDButtonPress), for: .touchUpInside)
    self.loginProviderStackView.addArrangedSubview(authorizationButton)
}

留意: 将 Sign in with Apple 按钮增加到 Storyboard 时,还必须在 Xcode 的 Identity Inspector 中将控件的类值设置为 ASAuthorizationAppleIDButton

  1. 运用 Apple ID 恳求授权:

当用户点击 Sign in with Apple 按钮时,视图控制器调用 handleAuthorizationAppleIDButtonPress() 函数,该函数经过对用户的全名和电子邮件地址履行授权恳求来发动身份验证流程。 然后体系会检查用户是否在设备上运用他们的 Apple ID 登录。 假如用户未在体系级别登录,应用程序会弹出提示,告诉用户在“设置”中运用其 Apple ID 登录。

@objc
func handleAuthorizationAppleIDButtonPress() {
    let appleIDProvider = ASAuthorizationAppleIDProvider()
    let request = appleIDProvider.createRequest()
    request.requestedScopes = [.fullName, .email]
    let authorizationController = ASAuthorizationController(authorizationRequests: [request])
    authorizationController.delegate = self
    authorizationController.presentationContextProvider = self
    authorizationController.performRequests()
}

留意: 用户必须启用两层身份验证才干运用经过 Apple 登录,这样才干安全地拜访帐户。

授权控制器调用 presentationAnchorForAuthorizationController: 函数从应用程序中获取窗口,在该窗口中,它在模式表中向用户呈现 Sign in with Apple 内容。

func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
    return self.view.window!
}

假如用户运用 Apple ID 在体系级别登录,则会呈现描述“运用 Apple 登录”功用的列表,答应用户修改其帐户中的信息。用户能够修改他们的名字和姓氏,挑选另一个电子邮件地址作为他们的联络方法,并在应用程序中躲藏他们的电子邮件地址。假如用户挑选在应用程序中躲藏他们的电子邮件地址,Apple 会生成一个署理电子邮件地址,以将其电子邮件转发到用户的私人电子邮件地址。 终究,用户输入 Apple ID 的暗码,然后单击持续创立帐户。

  1. 处理用户凭证:

假如身份验证成功,授权控制器调用 authenticationController:didCompleteWithAuthorization: 托付函数,应用程序运用该函数将用户数据存储在钥匙串中。

func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
    switch authorization.credential {
    case let appleIDCredential as ASAuthorizationAppleIDCredential:
        // Create an account in your system.
        let userIdentifier = appleIDCredential.user
        let fullName = appleIDCredential.fullName
        let email = appleIDCredential.email
        // For the purpose of this demo app, store the `userIdentifier` in the keychain.
        self.saveUserInKeychain(userIdentifier)
        // For the purpose of this demo app, show the Apple ID credential information in the `ResultViewController`.
        self.showResultViewController(userIdentifier: userIdentifier, fullName: fullName, email: email)
    case let passwordCredential as ASPasswordCredential:
        // Sign in using an existing iCloud Keychain credential.
        let username = passwordCredential.user
        let password = passwordCredential.password
        // For the purpose of this demo app, show the password credential as an alert.
        DispatchQueue.main.async {
            self.showPasswordCredentialAlert(username: username, password: password)
        }
    default:
        break
    }
}
  • User ID:Unique, stable, team-scoped user ID,苹果用户仅有标识符,该值在同一个开发者账号下的所有 App 下是相同的,开发者能够用该仅有标识符与自己后台体系的账号体系绑定起来。

  • Verification data:Identity token, code,验证数据,用于传给开发者后台服务器,然后开发者服务器再向苹果的身份验证服务端验证本次授权登录恳求数据的有用性和实在性,详见Sign in with Apple REST API。假如验证成功,能够依据 userIdentifier 判别账号是否已存在,若存在,则回来自己账号体系的登录态,若不存在,则创立一个新的账号,并回来对应的登录态给 App。

  • Account information:Name, verified email,苹果用户信息,包括全名、邮箱等。

  • Real user indicator:High confidence indicator that likely real user,用于判别当时登录的苹果账号是否是一个实在用户,取值有:unsupported、unknown、likelyReal。

留意: 在代码实现中,ASAuthorizationControllerDelegate.authorizationController(controller:didCompleteWithAuthorization:) 托付函数应该运用用户标识符中包括的数据在咱们的体系中创立一个帐户。

假如认证失败,授权控制器调用 authorizationController:didCompleteWithError: 托付函数来处理错误。

func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
    // Handle error.
}

一旦体系对用户进行身份验证,应用程序就会显现 ResultViewController,它显现从结构恳求的用户信息,包括用户供给的全名和电子邮件地址。视图控制器还显现一个退出按钮并将用户数据存储在钥匙串中。当用户点击退出按钮时,应用程序会从视图控制器和钥匙串中删去用户信息,并将 LoginViewController 呈现给用户。

  1. 恳求现有凭证 LoginViewController.performExistingAccountSetupFlows() 函数经过恳求 Apple ID 和 iCloud 钥匙串暗码来检查用户是否有现有帐户。 与 handleAuthorizationAppleIDButtonPress() 类似,授权控制器设置其表明内容供给者并托付给 LoginViewController 目标。
func performExistingAccountSetupFlows() {
    // Prepare requests for both Apple ID and password providers.
    let requests = [ASAuthorizationAppleIDProvider().createRequest(),                    ASAuthorizationPasswordProvider().createRequest()]
    // Create an authorization controller with the given requests.
    let authorizationController = ASAuthorizationController(authorizationRequests: requests)
    authorizationController.delegate = self
    authorizationController.presentationContextProvider = self
    authorizationController.performRequests()
}

authorizationController(controller:didCompleteWithAuthorization:) 托付函数检查凭证是 Apple ID (ASAuthorizationAppleIDCredential) 仍是暗码凭证 (ASPasswordCredential)。假如凭证是暗码凭证,体系会显现一条警报,答应用户运用现有帐户进行身份验证。

  1. 在发动时检查用户凭证

当开发者的 App 经过苹果账号登录后,iOS/macOS 设备上登录的 Apple ID 发生变化时,例如:

  • 设备上的 Apple ID 退出登录、切换新的账号登录;
  • 用户在设置页面制止 App 运用苹果账号登录

此时,也需求告诉到 App 做账号登出处理。因而,咱们能够 App 发动时,调用ASAuthorizationAppleIDProvidergetCredentialState方法,传入当时用户的 UserIdentifier 进行判别:

示例应用仅在必要时显现运用 Apple 登录用户界面。 应用程序托付在 AppDelegate.application(_:didFinishLaunchingWithOptions:) 函数中发动后立即检查保存的用户凭证的状况。

getCredentialStateForUserID:completion: 函数检索保存在钥匙串中的用户标识符的状况。假如用户授予应用程序授权(例如,用户在设备上运用其 Apple ID 登录应用程序),则应用程序将持续履行。假如用户吊销了对应用程序的授权,或许未找到用户的凭证状况,应用程序将经过调用 showLoginViewController() 函数显现登录表单。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let appleIDProvider = ASAuthorizationAppleIDProvider()
    appleIDProvider.getCredentialState(forUserID: KeychainItem.currentUserIdentifier) { (credentialState, error) in
        switch credentialState {
        case .authorized:
            break // The Apple ID credential is valid.
        case .revoked, .notFound:
            // The Apple ID credential is either revoked or was not found, so show the sign-in UI.
            DispatchQueue.main.async {
                self.window?.rootViewController?.showLoginViewController()
            }
        default:
            break
        }
    }
    return true
}

苹果称,该 API 的速度非常快,咱们能够在 App 每次发动时调用,然后依据成果做相应的处理:

  • authorized:登录状况有用;
  • revoked:前次运用苹果账号登录的凭证已被移除,需退出解除绑定并重新引导运用苹果登录;
  • notFound:未登录,直接显现开发者 App 的登录页面。

此外,苹果也供给了告诉的方法来监听,在 App 运用过程中,当苹果账号发生变化时做相应的处理:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedAppleIDCredentialRevokedNotification:) name:ASAuthorizationAppleIDProviderCredentialRevokedNotification object:nil];
- (void)receivedAppleIDCredentialRevokedNotification:(NSNotification *)notification {
    // oops
}

后端实现

  1. 客户端与 Server 端处理流程如下:

iOS 探究 | 第九篇 Sign in with Apple 及撤销令牌(Revoke Tokens)详细探究

Server 端供给一个接口接纳 identityTokenauthorizationCodeuserID 并进行有用性验证,详细参数意义,上文已介绍过。

  1. 校验身份(Verify the Identity Token): 首先将身份令牌和授权代码安全地传输到咱们的 Server 端,有 Server 端调用苹果 API 去校验。有关验证用户身份所需信息的更多信息可参阅:Generate and validate tokens。假如验证成功,能够依据 userIdentifier 判别账号是否已存在。假如存在,则回来账号相关信息,若不存在,则创立一个新的账号,并回来相应的信息给客户端。

要验证身份令牌,Server 端必须要验证如下:

  • 运用服务器的公钥验证 JWS E256 签名

  • 验证 nonce 以进行身份验证

  • 验证 iss 字段是否包括 appleid.apple.com

  • 验证 aud 字段是开发者的 client_id 即 bundle id

  • 验证时刻是否早于 token 的 exp 值

关于生成苹果服务器验证合法性所需 JWT 格局的 client_secret 所需参数: private key:苹果开发者账号中创立,只能下载一次(.P8格局)需求妥善保管(上文已提到)

  • alg: 用于签署令牌的算法。对于运用 Apple 登录,需求运用 ES256。

  • kid: 与开发者帐户相关的 Sign in with Apple 私钥生成的 10 个字符的密钥标识符,即开发者账号中创立的私钥对应的 Key ID。

JWT payload 包括特定于 Sign in with Apple REST API 和客户端应用程序的信息,例如颁发者、主题和到期时刻。在 payload 中运用以下参数:

  • iss: 发布者注册的标识 client secret 的主体。因为 client secret 归于开发人员团队,因而请运用与开发帐户相关的 10 个字符的 Team ID。

  • iat: 在注册时声明表明生成 client secret 的时刻,以自 Epoch 以来的秒数表明,以 UTC 秒表明。

  • exp: 已注册的过期时刻标识 client secret 过期的时刻或许说是之后多久过期。与服务器上的当时 UNIX 时刻比较,该值不得大于 15777000(6 个月,以秒为单位)。

  • aud: 声明标识 client secret 的预期接纳者。因为 client secret 被发送到验证服务器,因而请运用 appleid.apple.com 固定值。

  • sub: 声明标识作为 client secret 主体。因为 client secret 只适用于当时的应用程序,因而请运用与 client_id 相同的值,即 bundle id。该值区分大小写。

创立 JWT 后,运用椭圆曲线数字签名算法 (ECDSA) 和 P-256 曲线和 SHA-256 哈希算法对其进行签名。解码的 client_secret JWT 令牌具有以下格局:

{
    "alg": "ES256",
    "kid": "AB2CDE588F"
}
{
    "iss": "EFGH6X6XYZ",
    "iat": 1437179036,
    "exp": 15777000,
    "aud": "https://appleid.apple.com",
    "sub": "com.minhechen.test"
}

无论在运用 Apple REST API 登录时运用哪种编程语言,都有各种在线开源库可用于创立和签署 JWT 令牌。有关更多信息可检查JWT.io。

  1. 苹果后台验证 Token 的接口: Server 端经过调用 appleid.apple.com/auth/token 接口回来的数据和客户端经过接口传过来的数据进行校验,接口详细运用,可参阅官方文档 Generate and Validate Tokens

  2. 验证经往后,检查账户绑定状况,假如已绑定则回来用户相关信息,假如未绑定则进行绑定相关操作。


关于解绑

苹果于2022 年 5 月 24 日发布了一则声明,要求支撑帐户创立的 App 必须同时答应用户在 App 中建议帐户删去:

iOS 探究 | 第九篇 Sign in with Apple 及撤销令牌(Revoke Tokens)详细探究

从技能的视点简略来说,最首要的便是两件事:

  1. App 要支撑帐户删去。
  2. Apple 登录的帐户需求调用 REST API 来吊销用户令牌。

帐户删去这儿就不多说了,现在市面上的 App 基本上都已支撑该功用。 这儿要点说一下调用 REST API 来吊销用户令牌。吊销用户令牌的接口为:appleid.apple.com/auth/revoke 官方文档:Revoke Tokens

iOS 探究 | 第九篇 Sign in with Apple 及撤销令牌(Revoke Tokens)详细探究

接口所需参数,上文已经介绍过,这儿不再赘述,要点介绍一下 client_secret 的生成,这个参数一般都是 Server 端生成,但客户端也能够生成(坏处就很明显了,如:private key 端里写死且不安全,且无法更新等等,这儿暂不讨论),下面要点介绍一下端内怎么生成。

先准备好 Header 及 Payload:

extension Data {
  func urlSafeBase64EncodedString() -> String {
    return base64EncodedString()
      .replacingOccurrences(of: "+", with: "-")
      .replacingOccurrences(of: "/", with: "_")
      .replacingOccurrences(of: "=", with: "")
  }
}
struct Header: Encodable {
    let alg = "ES256"
    let kid = "AB2CDE588F"
}
struct Payload: Encodable {
    let iss = "EFGH6X6XYZ"
    let aud = "https://appleid.apple.com"
    let exp = "15777000"
    let iat = Date().timeIntervalSince1970
    let sub = "com.minhechen.test"
}
class func getJWTClientSecret() -> String {
    let secret = "-----BEGIN PRIVATE KEY-----\nMafsaaADFADGSM49AwEHBHkwqu22q\nllUYYiBl6j0DAQehRANSFTnJvB6qBR\nHe5CSAFDmNPGX+k1adssdFSwTBADny9zpsQg6HY2BCVsfsL\nKn/nesacK\n-----END PRIVATE KEY-----"
    let privateKey = SymmetricKey(data: Data(secret.utf8))
    let headerJSONData = try! JSONEncoder().encode(Header())
    let headerBase64String = headerJSONData.urlSafeBase64EncodedString()
    let payloadJSONData = try! JSONEncoder().encode(Payload())
    let payloadBase64String = payloadJSONData.urlSafeBase64EncodedString()
    let toSign = Data((headerBase64String + "." + payloadBase64String).utf8)
    let signature = HMAC<SHA256>.authenticationCode(for: toSign, using: privateKey)
    let signatureBase64String = Data(signature).urlSafeBase64EncodedString()
    let token = [headerBase64String, payloadBase64String, signatureBase64String].joined(separator: ".")
    return token
}

留意: 私钥中,除了私钥的开头“—–BEGIN PRIVATE KEY—–”和结束“—–END PRIVATE KEY—–”独自一行,其他每一行需求每64个字符换一行,上面代码中的 “\n” 即为换行符。

正常来说,调用 auth/revoke 接口会回来状况码:200,就意味着 App 登录吊销令牌成功,之后就能够持续其他业务流程了。至此 Revoke tokens 功用就实现了。


总结

最近恰巧在项目中做了 Revoke tokens 功用,需求几经周折,终究确认的方案仍是由客户端调用 Server 端接口进行令牌吊销。实践开发中也会涉及到法务及隐式协议相关的留意事项,每个产品可依据各自的特色进行灵敏调整。总之依照苹果巴巴说的做就行了,不配合的终究都得叫巴巴。以上便是本文关于 Sign in with apple 的完好介绍,希望这篇文章对你有所帮助,感谢阅读。

参阅资料:

Implementing User Authentication with Sign in with Apple

Generate and Validate Tokens

Verifying a User

Revoke Tokens


关于技能组

iOS 技能组首要用来学习、分享日常开发中运用到的技能,一起保持学习,保持前进。文章仓库在这儿:github.com/minhechen/i… 微信大众号:iOS技能组,欢迎联络进群学习交流,感谢阅读。

iOS 探究 | 第九篇 Sign in with Apple 及撤销令牌(Revoke Tokens)详细探究