在 Xamarin.iOS 中使用 Apple 登录
使用 Apple 登录是一项新服务,为第三方身份验证服务的用户提供标识保护。 从 iOS 13 开始,Apple 要求使用第三方身份验证服务的任何新应用也应提供 Apple 登录。 在 2020 年 4 月之前,更新的现有应用无需添加 Apple 登录。
本文档介绍了如何将使用 Apple 登录添加到 iOS 13 应用程序。
Apple 开发人员设置
在使用 Apple 登录生成和运行应用之前,需要完成这些步骤。 在 Apple 开发人员证书、标识符和配置文件门户上:
- 创建新的应用 ID 标识符。
- 在“说明”字段中设置说明。
- 选择显式 捆绑 ID 并在字段中设置
com.xamarin.AddingTheSignInWithAppleFlowToYourApp
。 - 启用“使用 Apple 登录”功能并注册新标识。
- 使用新的标识创建新的预配配置文件。
- 在设备上下载并安装它。
- 在 Visual Studio 中,在 Entitlements.plist 文件中启用使用 Apple 登录功能。
检查登录状态
应用启动时,或首次需要检查用户的身份验证状态时,实例化 ASAuthorizationAppleIdProvider
并检查当前状态:
var appleIdProvider = new ASAuthorizationAppleIdProvider ();
appleIdProvider.GetCredentialState (KeychainItem.CurrentUserIdentifier, (credentialState, error) => {
switch (credentialState) {
case ASAuthorizationAppleIdProviderCredentialState.Authorized:
// The Apple ID credential is valid.
break;
case ASAuthorizationAppleIdProviderCredentialState.Revoked:
// The Apple ID credential is revoked.
break;
case ASAuthorizationAppleIdProviderCredentialState.NotFound:
// No credential was found, so show the sign-in UI.
InvokeOnMainThread (() => {
var storyboard = UIStoryboard.FromName ("Main", null);
if (!(storyboard.InstantiateViewController (nameof (LoginViewController)) is LoginViewController viewController))
return;
viewController.ModalPresentationStyle = UIModalPresentationStyle.FormSheet;
viewController.ModalInPresentation = true;
Window?.RootViewController?.PresentViewController (viewController, true, null);
});
break;
}
});
在此代码中,在 AppDelegate.cs
的 FinishedLaunching
期间调用,应用将在状态 NotFound
时处理,并将 LoginViewController
呈现给用户。 如果状态已返回 Authorized
或 Revoked
,则可能会向用户显示其他操作。
使用 Apple 登录的 LoginViewController
实现登录逻辑并提供使用 Apple 登录的 UIViewController
需要实现 IASAuthorizationControllerDelegate
和 IASAuthorizationControllerPresentationContextProviding
,如以下示例中的 LoginViewController
所示。
public partial class LoginViewController : UIViewController, IASAuthorizationControllerDelegate, IASAuthorizationControllerPresentationContextProviding {
public LoginViewController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Perform any additional setup after loading the view, typically from a nib.
SetupProviderLoginView ();
}
public override void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
PerformExistingAccountSetupFlows ();
}
void SetupProviderLoginView ()
{
var authorizationButton = new ASAuthorizationAppleIdButton (ASAuthorizationAppleIdButtonType.Default, ASAuthorizationAppleIdButtonStyle.White);
authorizationButton.TouchUpInside += HandleAuthorizationAppleIDButtonPress;
loginProviderStackView.AddArrangedSubview (authorizationButton);
}
// Prompts the user if an existing iCloud Keychain credential or Apple ID credential is found.
void PerformExistingAccountSetupFlows ()
{
// Prepare requests for both Apple ID and password providers.
ASAuthorizationRequest [] requests = {
new ASAuthorizationAppleIdProvider ().CreateRequest (),
new ASAuthorizationPasswordProvider ().CreateRequest ()
};
// Create an authorization controller with the given requests.
var authorizationController = new ASAuthorizationController (requests);
authorizationController.Delegate = this;
authorizationController.PresentationContextProvider = this;
authorizationController.PerformRequests ();
}
private void HandleAuthorizationAppleIDButtonPress (object sender, EventArgs e)
{
var appleIdProvider = new ASAuthorizationAppleIdProvider ();
var request = appleIdProvider.CreateRequest ();
request.RequestedScopes = new [] { ASAuthorizationScope.Email, ASAuthorizationScope.FullName };
var authorizationController = new ASAuthorizationController (new [] { request });
authorizationController.Delegate = this;
authorizationController.PresentationContextProvider = this;
authorizationController.PerformRequests ();
}
}
此示例代码检查 PerformExistingAccountSetupFlows
中的当前登录状态,并作为委托连接到当前视图。 如果找到现有的 iCloud 密钥链凭据或 Apple ID 凭据,系统会提示用户使用该凭据。
Apple 提供 ASAuthorizationAppleIdButton
,这是一个专门用于此目的的按钮。 触摸时,该按钮将触发方法 HandleAuthorizationAppleIDButtonPress
中处理的工作流。
处理授权
在 IASAuthorizationController
实现任何自定义逻辑来存储用户帐户。 以下示例将用户帐户存储在 Apple 自己的存储服务 Keychain 中。
#region IASAuthorizationController Delegate
[Export ("authorizationController:didCompleteWithAuthorization:")]
public void DidComplete (ASAuthorizationController controller, ASAuthorization authorization)
{
if (authorization.GetCredential<ASAuthorizationAppleIdCredential> () is ASAuthorizationAppleIdCredential appleIdCredential) {
var userIdentifier = appleIdCredential.User;
var fullName = appleIdCredential.FullName;
var email = appleIdCredential.Email;
// Create an account in your system.
// For the purpose of this demo app, store the userIdentifier in the keychain.
try {
new KeychainItem ("com.example.apple-samplecode.juice", "userIdentifier").SaveItem (userIdentifier);
} catch (Exception) {
Console.WriteLine ("Unable to save userIdentifier to keychain.");
}
// For the purpose of this demo app, show the Apple ID credential information in the ResultViewController.
if (!(PresentingViewController is ResultViewController viewController))
return;
InvokeOnMainThread (() => {
viewController.UserIdentifierText = userIdentifier;
viewController.GivenNameText = fullName?.GivenName ?? "";
viewController.FamilyNameText = fullName?.FamilyName ?? "";
viewController.EmailText = email ?? "";
DismissViewController (true, null);
});
} else if (authorization.GetCredential<ASPasswordCredential> () is ASPasswordCredential passwordCredential) {
// Sign in using an existing iCloud Keychain credential.
var username = passwordCredential.User;
var password = passwordCredential.Password;
// For the purpose of this demo app, show the password credential as an alert.
InvokeOnMainThread (() => {
var message = $"The app has received your selected credential from the keychain. \n\n Username: {username}\n Password: {password}";
var alertController = UIAlertController.Create ("Keychain Credential Received", message, UIAlertControllerStyle.Alert);
alertController.AddAction (UIAlertAction.Create ("Dismiss", UIAlertActionStyle.Cancel, null));
PresentViewController (alertController, true, null);
});
}
}
[Export ("authorizationController:didCompleteWithError:")]
public void DidComplete (ASAuthorizationController controller, NSError error)
{
Console.WriteLine (error);
}
#endregion
授权控制器
此实现的最后一部分是管理提供程序的授权请求的 ASAuthorizationController
。
#region IASAuthorizationControllerPresentation Context Providing
public UIWindow GetPresentationAnchor (ASAuthorizationController controller) => View.Window;
#endregion