如果你的应用接入了第三方登陆,那么请同时接入苹果登录。
苹果审核指南的相关内容 新闻:2020年4月前需要适配好苹果登录
简单接入 苹果登录官方文档
流程:
用户点击按钮 — 调起苹果登录 — 授权成功 — 获取唯一标识符和其他信息 — 返回给后端 — 后端注册/登录 — 返回token — 登录成功
必要的工作
首先去苹果后台开启Sign in with apple
选项,然后重新导出provisionprofile
证书
授权
1 2 3 4 5 6 7 8 9 10 11 12 ASAuthorizationAppleIDProvider *provider = [[ASAuthorizationAppleIDProvider alloc] init]; ASAuthorizationAppleIDRequest *request = [provider createRequest]; request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail]; NSMutableArray <ASAuthorizationRequest *>* array = [NSMutableArray arrayWithCapacity:2]; if (request) [array addObject:request]; NSArray<ASAuthorizationRequest *> *requests = [array copy]; ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:requests]; authorizationController.delegate = self; authorizationController.presentationContextProvider = input; [authorizationController performRequests];
回调,遵循回调ASAuthorizationControllerPresentationContextProviding
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)) { if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) { // 用户登录使用ASAuthorizationAppleIDCredential ASAuthorizationAppleIDCredential *appleIDCredential = (ASAuthorizationAppleIDCredential *)authorization.credential; NSString *user = appleIDCredential.user; NSString *namePerfix = appleIDCredential.fullName.namePrefix; NSString *givenName = appleIDCredential.fullName.givenName; NSString *middleName = appleIDCredential.fullName.middleName; NSString *familyName = appleIDCredential.fullName.familyName; NSString *nameSuffix = appleIDCredential.fullName.nameSuffix; NSString *email = appleIDCredential.email; NSString *nickname = appleIDCredential.fullName.nickname; if (!nickname || nickname.length == 0) { nickname = [NSString stringWithFormat:@"%@%@%@%@%@",namePerfix?:@"",familyName?:@"",givenName?:@"",middleName?:@"",nameSuffix?:@""]; } } else { [self.errorSubject sendNext:LMError(@"授权信息有误")]; } } - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)) { NSString *errorMsg = nil; switch (error.code) { case ASAuthorizationErrorCanceled: errorMsg = @"用户取消了授权请求"; return; case ASAuthorizationErrorFailed: errorMsg = @"授权请求失败"; break; case ASAuthorizationErrorInvalidResponse: errorMsg = @"授权请求响应无效"; break; case ASAuthorizationErrorNotHandled: errorMsg = @"未能处理授权请求"; break; case ASAuthorizationErrorUnknown: errorMsg = @"授权请求失败未知原因"; break; } [self.errorSubject sendNext:LMError(errorMsg)]; }
其他可选项 苹果提供的登录按钮 1 ASAuthorizationAppleIDButton *button = [ASAuthorizationAppleIDButton buttonWithType:ASAuthorizationAppleIDButtonTypeSignIn style:ASAuthorizationAppleIDButtonStyleWhiteOutline];
其中
1 2 3 4 5 6 7 8 9 10 11 12 13 typedef NS_ENUM(NSInteger, ASAuthorizationAppleIDButtonType) { ASAuthorizationAppleIDButtonTypeSignIn, // 按钮文字显示 :通过Apple登录 ASAuthorizationAppleIDButtonTypeContinue, // 按钮文字显示 :通过Apple继续 ASAuthorizationAppleIDButtonTypeDefault = // 默认第一个 ASAuthorizationAppleIDButtonTypeSignIn, } typedef NS_ENUM(NSInteger, ASAuthorizationAppleIDButtonStyle) { ASAuthorizationAppleIDButtonStyleWhite, // 白底黑字 ASAuthorizationAppleIDButtonStyleWhiteOutline, // 黑字白框 ASAuthorizationAppleIDButtonStyleBlack, // 黑底白字 }
授权成功的回调可以来自于其他地方 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 //! 授权成功地回调 - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)){ NSLog(@"%s", __FUNCTION__); NSLog(@"%@", controller); NSLog(@"%@", authorization); NSLog(@"authorization.credential:%@", authorization.credential); NSMutableString *mStr = [NSMutableString string]; mStr = [_appleIDInfoTextView.text mutableCopy]; if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) { // 用户登录使用ASAuthorizationAppleIDCredential ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential; NSString *user = appleIDCredential.user; // 最好使用钥匙串的方式保存用户的唯一信息 这里暂且处于测试阶段,用NSUserDefaults [[NSUserDefaults standardUserDefaults] setValue:user forKey:QiShareCurrentIdentifier]; [mStr appendString:user?:@""]; NSString *familyName = appleIDCredential.fullName.familyName; [mStr appendString:familyName?:@""]; NSString *givenName = appleIDCredential.fullName.givenName; [mStr appendString:givenName?:@""]; NSString *email = appleIDCredential.email; [mStr appendString:email?:@""]; NSLog(@"mStr:%@", mStr); [mStr appendString:@"\n"]; _appleIDInfoTextView.text = mStr; } else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]) { // 用户登录使用现有的密码凭证 ASPasswordCredential *passwordCredential = authorization.credential; // 密码凭证对象的用户标识 用户的唯一标识 NSString *user = passwordCredential.user; // 密码凭证对象的密码 NSString *password = passwordCredential.password; [mStr appendString:user?:@""]; [mStr appendString:password?:@""]; [mStr appendString:@"\n"]; NSLog(@"mStr:%@", mStr); _appleIDInfoTextView.text = mStr; } else { NSLog(@"授权信息均不符"); mStr = [@"授权信息均不符" mutableCopy]; _appleIDInfoTextView.text = mStr; } }
已经使用Sign In With Apple登录过app的用户 执行已经登录过的场景。如果设备中存在iCloud Keychain 凭证或者AppleID 凭证提示用户直接使用TouchID或FaceID登录即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 - (void)perfomExistingAccountSetupFlows { if (@available(iOS 13.0, *)) { // A mechanism for generating requests to authenticate users based on their Apple ID. // 基于用户的Apple ID授权用户,生成用户授权请求的一种机制 ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new]; // An OpenID authorization request that relies on the user’s Apple ID. // 授权请求依赖于用于的AppleID ASAuthorizationAppleIDRequest *authAppleIDRequest = [appleIDProvider createRequest]; // A mechanism for generating requests to perform keychain credential sharing. // 为了执行钥匙串凭证分享生成请求的一种机制 ASAuthorizationPasswordRequest *passwordRequest = [[ASAuthorizationPasswordProvider new] createRequest]; NSMutableArray <ASAuthorizationRequest *>* mArr = [NSMutableArray arrayWithCapacity:2]; if (authAppleIDRequest) { [mArr addObject:authAppleIDRequest]; } if (passwordRequest) { [mArr addObject:passwordRequest]; } // ASAuthorizationRequest:A base class for different kinds of authorization requests. // ASAuthorizationRequest:对于不同种类授权请求的基类 NSArray <ASAuthorizationRequest *>* requests = [mArr copy]; // A controller that manages authorization requests created by a provider. // 由ASAuthorizationAppleIDProvider创建的授权请求 管理授权请求的控制器 // Creates a controller from a collection of authorization requests. // 从一系列授权请求中创建授权控制器 ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:requests]; // A delegate that the authorization controller informs about the success or failure of an authorization attempt. // 设置授权控制器通知授权请求的成功与失败的代理 authorizationController.delegate = self; // A delegate that provides a display context in which the system can present an authorization interface to the user. // 设置提供 展示上下文的代理,在这个上下文中 系统可以展示授权界面给用户 authorizationController.presentationContextProvider = self; // starts the authorization flows named during controller initialization. // 在控制器初始化期间启动授权流 [authorizationController performRequests]; } }
监听授权状态变化 监听授权状态改变,并且做出相应处理。授权状态有:
1 2 3 ASAuthorizationAppleIDProviderCredentialRevoked:授权状态失效(用户停止使用AppID 登录App) ASAuthorizationAppleIDProviderCredentialAuthorized:已授权(已使用AppleID 登录过App) ASAuthorizationAppleIDProviderCredentialNotFound:授权凭证缺失(可能是使用AppleID 登录过App)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 //! 观察授权状态 - (void)observeAuthticationState { if (@available(iOS 13.0, *)) { // A mechanism for generating requests to authenticate users based on their Apple ID. // 基于用户的Apple ID 生成授权用户请求的机制 ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new]; // 注意 存储用户标识信息需要使用钥匙串来存储 这里笔者简单期间 使用NSUserDefaults 做的简单示例 NSString *userIdentifier = [[NSUserDefaults standardUserDefaults] valueForKey:QiShareCurrentIdentifier]; if (userIdentifier) { NSString* __block errorMsg = nil; //Returns the credential state for the given user in a completion handler. // 在回调中返回用户的授权状态 [appleIDProvider getCredentialStateForUserID:userIdentifier completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError * _Nullable error) { switch (credentialState) { // 苹果证书的授权状态 case ASAuthorizationAppleIDProviderCredentialRevoked: // 苹果授权凭证失效 errorMsg = @"苹果授权凭证失效"; break; case ASAuthorizationAppleIDProviderCredentialAuthorized: // 苹果授权凭证状态良好 errorMsg = @"苹果授权凭证状态良好"; break; case ASAuthorizationAppleIDProviderCredentialNotFound: // 未发现苹果授权凭证 errorMsg = @"未发现苹果授权凭证"; // 可以引导用户重新登录 break; } dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"SignInWithApple授权状态变化情况"); NSLog(@"%@", errorMsg); }); }]; } } }
使用通知的方式检测是否授权应用支持Sign In With Apple变化情况。如下的代码可以根据自己的业务场景去考虑放置的位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 //! 添加苹果登录的状态通知 - (void)observeAppleSignInState { if (@available(iOS 13.0, *)) { NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center addObserver:self selector:@selector(handleSignInWithAppleStateChanged:) name:ASAuthorizationAppleIDProviderCredentialRevokedNotification object:nil]; } } //! 观察SignInWithApple状态改变 - (void)handleSignInWithAppleStateChanged:(id)noti { NSLog(@"%s", __FUNCTION__); NSLog(@"%@", noti); } - (void)dealloc { if (@available(iOS 13.0, *)) { [[NSNotificationCenter defaultCenter] removeObserver:self name:ASAuthorizationAppleIDProviderCredentialRevokedNotification object:nil]; } }
重要(Important!)
最好使用苹果提供的按钮 ASAuthorizationAppleIDButton
(只有黑白两种颜色)
不用他的按钮的话建议使用显眼的颜色
尽量放在显眼位置(第一位)
不能比其他任何登录按钮要小
保证登录页面一屏就能看到苹果登录按钮,不能滚动后才能看到
按钮的最小宽高有需求(看苹果人机交互指南相关文档 )
如果登录后要绑定手机的话,就在备注里面写好 依据来源 http://www.cac.gov.cn/2016-11/07/c_1119867116_2.htm 指明(截图)第二十四条(苹果一般不会打开网页,建议下载个pdf给他)
如果不好好跟苹果爸爸的规矩来,那么可能会吃到2.1和4.0 (不要问我为什么知道)
(更新至2019/11/01)
最小宽度
最小高度
最小间距
140pt (140px @1x, 280px @2x)
30pt (30px @1x, 60px @2x)
1/10 of the button’s height)
参考其他教程 掘金