一、为什么要使用Sign in with Apple?

苹果在19年 9 月12 号更新了审核指南,加入 4.8 Sign in with Apple 一条,要求所有使用第三方登录 的 App,都必须接入 Sign in with Apple。已经上架的 App 需在 2020 年 4 月 前完成接入工作,新上架 App(如果支持三方登录)必须接入Sign in with Apple,否则将被拒。

二、如何接入Sign In With Apple ?

1、前期准备

(1)、系统配置:苹果开发文档明确规定:苹果电脑系统macos(10.15)(吓的我赶紧升级我的笔记本系统),手机iOS系统ios(13.0)(毫不犹豫的升级)。由于只有Xcode11才支持Sign In With Apple接入代码的开发,所以Xcode我升级到了11.1正式版。
(2)、证书配置
登录开发者中心,在需要启用 Sign in with Apple 的 Apple ID中勾选 Sign in with Apple.

具体步骤如下图

登录苹果开发者中心,选择Account

选择 Identifiers 这边可以看到自己工程的 Bundle Identifier点进去勾选 Sign in with Apple 若没有添加一个


注意:

描述信息最好使用 XC + Bundle ID



勾选 Sign in with Apple

(3)、工程配置

新建工程,选择项目 TARGETS -> Signing&Capabilities ,单击下图中的 3:Capability:

在弹出框中搜索找到 Sign in with Apple:双击添加

这些完成后,我们就可以在项目中添加代码了。

2、添加代码

(1)、在需要使用 login with Apple 的地方,引入头文件:
#import <AuthenticationServices/AuthenticationServices.h>

首先,我们需要一个登录按钮,系统为我们预设了一个固定样式登录按钮ASAuthorizationAppleIDButton,我们可以直接使用。

if (@available(iOS 13.0, *)) {ASAuthorizationAppleIDButton *appleIDButton = [ASAuthorizationAppleIDButton new];appleIDButton.frame =  CGRectMake(.0, .0, CGRectGetWidth(self.view.frame) - 40.0, 100.0);CGPoint origin = CGPointMake(20.0, CGRectGetMidY(self.view.frame));CGRect frame = appleIDButton.frame;frame.origin = origin;appleIDButton.frame = frame;appleIDButton.cornerRadius = CGRectGetHeight(appleIDButton.frame) * 0.25;[self.view addSubview:appleIDButton];[appleIDButton addTarget:self action:@selector(handleAuthrization:) forControlEvents:UIControlEventTouchUpInside];}

通过参数 type、style可以设置为不同样式的按钮;当然,我们也可以自定义,但是要遵循苹果的相关设计规范,详见 Human Interface Guidelines

(2)、我们还需要用到两个协议
// 提供关于授权请求结果信息的接口
ASAuthorizationControllerDelegate,
// 控制器的代理找一个展示授权控制器的上下文的接口
ASAuthorizationControllerPresentationContextProviding>

协议的方法


#pragma mark - ASAuthorizationControllerDelegate// 授权成功地回调
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization  API_AVAILABLE(ios(13.0));// 授权失败的回调
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error  API_AVAILABLE(ios(13.0));#pragma mark - ASAuthorizationControllerPresentationContextProviding/*! @brief 返回弹出请求视图的window** @param ASPresentationAnchor  为 UIWindow 的别名* @param controller   管理授权请求的控制器* return 弹出请求视图的window*/
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller  API_AVAILABLE(ios(13.0));

以及 3个类

//主要作用是用创建相应的请求,查询用户授权状态
ASAuthorizationAppleIDProvider// 授权请求,可以设置具体的请求信息
ASAuthorizationAppleIDRequest// 发送请求控制器,可以设置相应的协议
ASAuthorizationController

首先在程序运行时会执行perfomExistingAccountSetupFlows 来判断如果存在iCloud Keychain 凭证或者AppleID 凭证提示用户

#pragma mark - 如果存在iCloud Keychain 凭证或者AppleID 凭证提示用户- (void)perfomExistingAccountSetupFlows {if (@available(iOS 13.0, *)) {// 基于用户的Apple ID授权用户,生成用户授权请求的一种机制ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];// 授权请求依赖于用于的AppleIDASAuthorizationAppleIDRequest *authAppleIDRequest = [appleIDProvider createRequest];// 为了执行钥匙串凭证分享生成请求的一种机制ASAuthorizationPasswordRequest *passwordRequest = [[ASAuthorizationPasswordProvider new] createRequest];NSMutableArray <ASAuthorizationRequest *>* mArr = [NSMutableArray arrayWithCapacity:2];if (authAppleIDRequest) {[mArr addObject:authAppleIDRequest];}if (passwordRequest) {[mArr addObject:passwordRequest];}// ASAuthorizationRequest:对于不同种类授权请求的基类NSArray <ASAuthorizationRequest *>* requests = [mArr copy];// ASAuthorizationController是由ASAuthorizationAppleIDProvider创建的授权请求 管理授权请求的控制器ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:requests];// 设置授权控制器通知授权请求的成功与失败的代理authorizationController.delegate = self;// 设置提供 展示上下文的代理,在这个上下文中 系统可以展示授权界面给用户authorizationController.presentationContextProvider = self;// 在控制器初始化期间启动授权流[authorizationController performRequests];}
}

通过点击按钮ASAuthorizationAppleIDButton 响应事件添加代理再执行相应的代理方法

#pragma mark - 点击授权按钮- (void)handleAuthrization:(UIButton *)sender {if (@available(iOS 13.0, *)) {// 基于用户的Apple ID授权用户,生成用户授权请求的一种机制ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];// 创建新的AppleID 授权请求ASAuthorizationAppleIDRequest *request = appleIDProvider.createRequest;// 在用户授权期间请求的联系信息request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];// 由ASAuthorizationAppleIDProvider创建的授权请求 管理授权请求的控制器ASAuthorizationController *controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];// 设置授权控制器通知授权请求的成功与失败的代理controller.delegate = self;// 设置提供 展示上下文的代理,在这个上下文中 系统可以展示授权界面给用户controller.presentationContextProvider = self;// 在控制器初始化期间启动授权流[controller performRequests];}
}
#pragma mark - 授权成功地回调- (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 *mutableString = [NSMutableString string];mutableString = [self.appleIDInfoTextView.text mutableCopy];if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {// 用户登录使用ASAuthorizationAppleIDCredentialASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential;NSString *user = appleIDCredential.user;// 使用钥匙串的方式保存用户的唯一信息NSString *bundleId = [NSBundle mainBundle].bundleIdentifier;[SAMKeychain setPassword:user forService:bundleId account:ShareCurrentIdentifier];[mutableString appendString:user?:@""];NSString *familyName = appleIDCredential.fullName.familyName;[mutableString appendString:familyName?:@""];NSString *givenName = appleIDCredential.fullName.givenName;[mutableString appendString:givenName?:@""];NSString *email = appleIDCredential.email;[mutableString appendString:email?:@""];NSLog(@"mStr:%@", mutableString);[mutableString appendString:@"\n"];self.appleIDInfoTextView.text = mutableString;} else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]) {// 用户登录使用现有的密码凭证ASPasswordCredential *passwordCredential = authorization.credential;// 密码凭证对象的用户标识 用户的唯一标识NSString *user = passwordCredential.user;// 密码凭证对象的密码NSString *password = passwordCredential.password;[mutableString appendString:user?:@""];[mutableString appendString:password?:@""];[mutableString appendString:@"\n"];NSLog(@"mStr:%@", mutableString);self.appleIDInfoTextView.text = mutableString;} else {NSLog(@"授权信息均不符");mutableString = [@"授权信息均不符" mutableCopy];self.appleIDInfoTextView.text = mutableString;}
}
#pragma mark - 授权失败的回调- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error  API_AVAILABLE(ios(13.0)) {NSLog(@"%s", __FUNCTION__);NSLog(@"错误信息:%@", error);NSString *errorMsg = nil;switch (error.code) {case ASAuthorizationErrorCanceled:errorMsg = @"用户取消了授权请求";break;case ASAuthorizationErrorFailed:errorMsg = @"授权请求失败";break;case ASAuthorizationErrorInvalidResponse:errorMsg = @"授权请求响应无效";break;case ASAuthorizationErrorNotHandled:errorMsg = @"未能处理授权请求";break;case ASAuthorizationErrorUnknown:errorMsg = @"授权请求失败未知原因";break;}NSMutableString *mStr = [self.appleIDInfoTextView.text mutableCopy];[mStr appendString:errorMsg];[mStr appendString:@"\n"];self.appleIDInfoTextView.text = [mStr copy];if (errorMsg) {return;}if (error.localizedDescription) {NSMutableString *mStr = [self.appleIDInfoTextView.text mutableCopy];[mStr appendString:error.localizedDescription];[mStr appendString:@"\n"];self.appleIDInfoTextView.text = [mStr copy];}NSLog(@"controller requests:%@", controller.authorizationRequests);
}
#pragma mark - 告诉代理应该在哪个window 展示内容给用户- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller  API_AVAILABLE(ios(13.0)){NSLog(@"调用展示window方法:%s", __FUNCTION__);// 返回windowreturn self.view.window;
}

点击按钮ASAuthorizationAppleIDButton 登录后,如果是未授权会弹出如下弹框:


这里的邮件地址,用户可以选择共享或者隐藏,如果选择了隐藏,开发者将会获得一个苹果自动生成的一个邮箱地址,而不是用户的真实邮箱;成功后返回的信息如下:

user: 001963.27e48e27b0e5853a7c5d744d9a1c5432.0701familyName: XX
givenName: XX
email: 你的邮箱

我们可以将 user 信息保存到钥匙串中,这里我用的是一个开源的第三方库

SAMKeychain

1、下载SAMKeychain.h、SAMKeychain.m、SAMKeychainQuery.h、SAMKeychainQuery.m这4个文件并导入项目中
2、在.m文件中引用(#import “SAMKeychain.h”)

如果我们已经授权登录成功,再次登录的时候,就会显示如下的页面:

我们再次读取时只会返回user的信息

app登录成功后,需要将获取到的 identityToken、code等信息发送给后台,然后由后台调用 Apple 的后台API,来验证用户的真实性,从而完成验证,详情参考 Sign in with Apple(苹果授权登陆)服务端验证

登录成功后,用户是可以随时取消授权的,或者用户将 AppleID退出了当前设备,都需要重新获取。我们可以在应用启动的时候使用下面的方法来检测用户状态

#pragma mark - 使用 user 信息,查询当前用户的状态- (void)checkAuthorizationStateWithUser:(NSString *) usercompleteHandler:(void(^)(BOOL authorized, NSString *msg)) completeHandler {if (user == nil || user.length <= 0) {if (completeHandler) {completeHandler(NO, @"用户标识符错误");}return;}ASAuthorizationAppleIDProvider *provider = [[ASAuthorizationAppleIDProvider alloc]init];[provider getCredentialStateForUserID:user completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError * _Nullable error) {NSString *msg = @"未知";BOOL authorized = NO;switch (credentialState) {case ASAuthorizationAppleIDProviderCredentialRevoked:msg = @"授权被撤销";authorized = NO;break;case ASAuthorizationAppleIDProviderCredentialAuthorized:msg = @"已授权";authorized = YES;break;case ASAuthorizationAppleIDProviderCredentialNotFound:msg = @"未查到授权信息";authorized = NO;break;case ASAuthorizationAppleIDProviderCredentialTransferred:msg = @"授权信息变动";authorized = NO;break;default:authorized = NO;break;}if (completeHandler) {completeHandler(authorized, msg);}}];
}

如果app在运行中,我们可以通过添加通知的方法来实时监控:

#pragma mark - 添加苹果登录的状态通知- (void)observeAppleSignInState {if (@available(iOS 13.0, *)) {NSNotificationCenter *center = [NSNotificationCenter defaultCenter];[center addObserver:self selector:@selector(handleSignInWithAppleStateChanged:) name:ASAuthorizationAppleIDProviderCredentialRevokedNotification object:nil];}
}
#pragma mark - 观察SignInWithApple状态改变- (void)handleSignInWithAppleStateChanged:(NSNotification *) noti {NSLog(@"%@", noti.name);NSLog(@"%@", noti.userInfo);
}

这里的 userInfo 信息一直都是 null,

3、取消授权

对应用授权登录后,我们可以在设备中取消对某个app的授权,操作方法如下:

设置 -> Apple ID -> 密码与安全性 -> 使用您 Apple ID 的 App

选择需要取消的app,停止使用 Apple ID 即可!

!! 但是这个方法只对 iphone 11 以上的手机才有效,11以下的手机还是展示已授权页面

4、Demo 地址 SignInWithAppleDemo

注意:以上属于原创,若有雷同纯属巧合;如有错误,请多多指正,如有遗漏,欢迎大家在评论区补充

iOS开发:使用 Sign In With Apple(登录)用法以及注意事项相关推荐

  1. iOS应用接入Sign In With Apple

    iOS应用接入Sign In With Apple流程 前言 准备工作 开始编写 总结 参考文献 前言 App Store审核要求: New Guidelines for Sign in with A ...

  2. iOS开发小知识之正则表达式的简单用法

    关于正则表达式,百度百科中是这样说的 正则表达式,又称正规表示法.常规表示法(英语:Regular Expression,在代码中常简写为regex.regexp或RE),计算机科学的一个概念.正则表 ...

  3. iOS开发-Please sign in with an app-specific password. You can create one at appleid.apple.com

    今天要更新APP,因为是新的账号,所以在Xcode无法登陆. 提示信息:Please sign in with an app-specific password. You can create one ...

  4. iOS开发最新最全微信第三方登录接入流程

    前言: [1] iOS 微信登录注意事项 1.目前移动应用上微信登录只提供原生的登录方式,需要用户安装微信客户端才能配合使用.2.对于Android应用,建议总是显示微信登录按钮,当用户手机没有安装微 ...

  5. (0013)iOS 开发之集成友盟第三方登录

    第三方登录的原理和流程 用户采用第三方登录的时候,用户会发送哪些信息到我的后台,后台会进行哪些比对操作?首次使用第三方登录和再次使用第三方登录时验证有哪些不同,后台如何保存用户的登录信息尼? 对于用户 ...

  6. iOS 开发,xcode7中用QQ授权登录遇到的一些问题 QQ登录不跳客户端

    1.首先导入framework. 2. 调用 _tencentOAuth = [[TencentOAuth alloc] initWithAppId:APPID andDelegate:self];这 ...

  7. macOS升级至12.1后,iOS开发证书无法安装到钥匙串“登录”目录下,无法导出P12文件问题解决方案

    背景: 由于要上传app至app store所以配置对应的开发上架证书,证书配置完成后需要共享给同组开发使用,需要导出P12身份信息文件. 问题描述: 按照常规安装方式,双击证书安装选择安装到&quo ...

  8. iOS开发 - 微信扫描二维码登录网页的原理

    转自: http://daily.zhihu.com/story/3783725 我个人开发过程一般是和产品说,『你们提业务要求.交互方式.性能要求等就好,技术方案我们会综合开发时间.系统架构等因素考 ...

  9. ios开发之--UITableView中的visibleCells的用法

    先上图: 具体代码如下: #import "ViewController.h"@interface ViewController ()<UITableViewDelegate ...

  10. iOS 苹果授权登录(Sign in with Apple)系列之Apple Developer配置篇

    原文 在 iOS13 中,如果 App 提供第三方登录,就必须添加 苹果登录 Sign in with Apple 选项,并要求所有开发者于 2020年4月之前 完成现有应用的更新,否则审核不给通过. ...

最新文章

  1. 浅谈OpenCL之 应用程序总体步骤思路
  2. QUIC - 低时延互联网传输层协议
  3. 新款iPhone SE发布日期曝光:小屏果粉早已按捺不住
  4. 正版python怎么下载_怎么下载官网python并安装
  5. 干活的不如写ppt的吗_“干活不如写PPT”为啥扎心?
  6. javascript之字符串常用方法学习 charAt concat indexOf substring substr toUpperCase
  7. 11-11 11:11
  8. FastDFS存储目录迁移方案
  9. 【pyqt5学习】——添加菜单栏动作action,给动作触发triggered绑定事件
  10. 根据IP地址求网络号、子网号和主机号
  11. rust使用vec在遍历时删除元素
  12. java.sql.SQLException: Incorrect string value: ‘\xE6\xB5\x8B\xE8\xAF\x95...‘ for column ‘xxx‘
  13. 【Spark】Spark Quick Start(快速入门翻译)
  14. fragment添加失败错误查找
  15. 电脑都面的没电了,我是如何通过腾讯云恐怖的一面面试的?
  16. post请求或get请求通过url传递参数
  17. Scala之case class
  18. 疫情期间,大型企业如何保障日常会议的正常进行?
  19. 微服务链路追踪SkyWalking第八课 OAP的receiver模块详解
  20. python开发cs软件_开发cs软件 c python

热门文章

  1. 2022年2月手术机器人行业动态
  2. android wifi tcpip,Android无线调试:tcpip无线连接 | WiFi apk无线连接
  3. macOS - pip install scipy Error: No BLAS/LAPACK libraries found
  4. ofstream和ifstream详细用法
  5. 因为被骗8000块,他愤而“自学”电信诈骗搞到115万,如今他这样
  6. 用机器学习打造聊天机器人(四) 代码篇
  7. JavaScript正则表达式匹配:不包含某字符或字符串
  8. 论文笔记 | 基于双线性CNN模型的细粒度视觉识别
  9. 龙之谷单机版(6月17日即将内测)【血量基址+偏移】
  10. Centos 7安装配置NTP网络时间同步服务器