这个示例 App“Juice”使用 AuthenticationServices 框架为用户提供了一个界面来设置账户并使用他们的 Apple ID 登录。该 App 提供了一个表单,用户可以通过表单在 App 中创建和设置账户,然后使用“通过 Apple 登录”对用户的 Apple ID 进行认证,并显示用户的账户数据。

有关在 iOS 12 及更早版本上实施“通过 Apple 登录”的更多信息,请参阅“将‘通过 Apple 登录’整合到其他平台中 (英文)”。

配置示例代码项目

要配置示例代码项目,请在 Xcode 中按照以下步骤操作:

在“Signing & Capabilities”(签名和功能) 面板中,将套装 ID 设置为唯一标识符 (必须更改套装 ID 才能继续)。

添加你的 Apple ID 账户 并将目标分配给团队,这样 Xcode 就可以使用预置描述文件启用“通过 Apple 登录”功能。

从方案弹出菜单中选择一个运行目的地,该目的地需使用 Apple ID 登录并使用双重认证。

如有必要,点按“Signing & Capabilities”(签名和功能) 面板中的“Register Device”(注册设备) 以创建预置描述文件。

在工具栏中,点按“Run”(运行),或选择“Product”(产品) >“Run”(运行) (⌘R)。

添加“通过 Apple 登录”按钮

在示例 App 中,LoginViewController 会在其视图层次结构中显示登录表单和“通过 Apple 登录”按钮 (ASAuthorizationAppleIDButton (英文))。视图控制器还会将自身添加为按钮的目标,并传递按钮收到触控事件时要调用的操作。

func setupProviderLoginView() {

let authorizationButton = ASAuthorizationAppleIDButton()

authorizationButton.addTarget(self, action: #selector(handleAuthorizationAppleIDButtonPress), for: .touchUpInside)

self.loginProviderStackView.addArrangedSubview(authorizationButton)

}

重要信息

在将“通过 Apple 登录”按钮添加到 Storyboard 时,你还必须在 Xcode 的 Identity Inspector 中将该控件的类值设置为 ASAuthorizationAppleIDButton。

使用 Apple ID 请求授权

当用户轻点“通过 Apple 登录”按钮时,视图控制器会调用 handleAuthorizationAppleIDButtonPress() 函数,该函数会执行授权请求来获取用户的全名和电子邮件地址,从而开始认证流程。然后,系统会检查用户是否在设备上登录了 Apple ID。如果用户没有在系统级登录,App 会显示一条提醒,指引用户在“设置”中使用 Apple ID 登录。

@objcfunc 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 登录”,这保证对账户的访问是安全的。

授权控制器会调用 presentationAnchor(for:) (英文) 函数来从 App 中获取窗口,用于将“通过 Apple 登录”内容以模态表单的形式呈现给用户。

func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {

return self.view.window!

}

如果用户已在系统级使用 Apple ID 登录,则会显示该表单来介绍“通过 Apple 登录”功能,然后会显示另一个表单,让用户可以编辑账户中的信息。用户可以编辑自己的名字和姓氏,选取另一个电子邮件地址作为联系信息,以及对 App 隐藏自己的电子邮件地址。如果用户选择对 App 隐藏自己的电子邮件地址,Apple 会生成一个代理电子邮件地址来将电子邮件转发到用户的私人电子邮件地址。最后,用户输入 Apple ID 的密码,然后点按“Continue”(继续) 以创建账户。

处理用户凭证

如果认证成功,授权控制器会调用 authorizationController(controller:didCompleteWithAuthorization:) (英文) 委托函数,App 使用该函数将用户的数据储存在钥匙串中。

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

}

}

注释

在你的实施中,ASAuthorizationControllerDelegate.authorizationController(controller:didCompleteWithAuthorization:) 委托函数应使用用户标识符中包含的数据在你的系统中创建一个账户。

如果认证失败,授权控制器会调用 authorizationController(controller:didCompleteWithError:) (英文) 委托函数来处理错误。

func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {

// Handle error.

}

系统对用户进行认证后,App 会显示 ResultViewController,该控制器显示从框架请求的用户信息,其中包括用户提供的全名和电子邮件地址。该视图控制器还会显示“Sign Out”(注销) 按钮,并将用户数据储存在钥匙串中。当用户轻点“Sign Out”(注销) 按钮时,App 会从视图控制器和钥匙串中删除用户信息,并向用户显示 LoginViewController。

请求现有的凭证

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 (英文))。如果是密码凭证,系统会显示一条提醒,允许用户使用现有账户进行认证。

启动时检查用户凭证

示例 App 仅在必要时显示“通过 Apple 登录”用户界面。App 委托会在 App 启动后立即通过 appDelegate.application(_:didFinishLaunchingWithOptions:) 函数检查已存储用户凭证的状态。

getCredentialState(forUserID:completion:) (英文) 函数会检索钥匙串中存储的用户标识符的状态。如果用户为 App 授权了 (例如,用户在设备上使用 Apple ID 登录了 App),那么 App 会继续执行。如果用户撤销了对 App 的授权,或者 App 找不到用户的凭证状态,App 会通过调用 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

}