iOS 13
新增功能Sign In with Apple
,Sign In with Apple
是跨平台的,可以支持iOS
、macOS
、watchOS
、tvOS
、JS
。最近上架AppStore
因为没有Sign In with Apple
被拒了,本文主要内容为Sign In with Apple
在iOS
上的使用。
项目配置
-
在苹果开发者网站,在对应的
设置SignInWithAppleAppleId
下面开通Sign In With Apple
的权限。如下图
-
现在我只需要在
Signing & Capabilities
中添加Sign In With Apple
,Xcode
会自动帮你配置生成相关文件。其实上面第一步可以省略,这里可以自动生成。
代码集成
Sign In With Apple
流程
- 导入系统头文件
#import <AuthenticationServices/AuthenticationServices.h>
,创建相关Sign In with Apple
登录按钮,添加按钮点击响应事件 - 获取授权码
- 验证
- 执行自己的相关逻辑
注意⚠️:创建按钮可以自定义或者使用系统提供的按钮。
实际操作
-
导入头文件
#import <AuthenticationServices/AuthenticationServices.h>
-
创建对应UI
-(void)setUI{ // 使用系统提供的按钮,要注意不支持系统版本的处理 if (@available(iOS 13.0, *)) { // Sign In With Apple Button ASAuthorizationAppleIDButton *btn_apple = [ASAuthorizationAppleIDButton buttonWithType:ASAuthorizationAppleIDButtonTypeDefault style:ASAuthorizationAppleIDButtonStyleWhite]; btn_apple.frame = CGRectMake(50, 100, self.view.bounds.size.width - 100, 100); [btn_apple addTarget:self action:@selector(signWithApple:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:btn_apple]; } // 或者自己用UIButton实现按钮样式 UIButton *btn_custom = [UIButton buttonWithType:UIButtonTypeCustom]; btn_custom.frame = CGRectMake(50, 250, self.view.bounds.size.width - 100, 44); [btn_custom setTitle:@"Sign in with Apple" forState:UIControlStateNormal]; [btn_custom setTitleColor:[UIColor grayColor] forState:UIControlStateNormal]; [btn_custom addTarget:self action:@selector(signWithApple:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:btn_custom]; } -(void)signWithApple:(UIButton *)sender{ [[SignWithAppleManger sharedManager]SignInWithAppleWithBlock:^(id _Nonnull data, BOOL success, BOOL showError) { NSLog(@"123"); }]; }
注意⚠️:封装Sign In with Apple
登录工具类,使用这个类时要把类对象设置为全局变量,或者直接把这个工具类做成单例,如果使用局部变量,和IAP支付工具类一样,会导致苹果回调不会执行。已经使用Sign In with Apple
登录过App
的用户如果设备中存在iCloud Keychain
凭证或者AppleID
凭证,提示用户直接使用TouchID
或FaceID
登录即可。
-(void)ExistingSignInWithAppleWithBlock:(SignBackInfoBlock)block{
self.block = block;
[self observeAppleSignInState];
if (@available(iOS 13.0, *)) {
// 基于用户的Apple ID授权用户,生成用户授权请求的一种机制
ASAuthorizationAppleIDProvider *appleIdProvider = [[ASAuthorizationAppleIDProvider alloc] init];
// 授权请求AppleID
ASAuthorizationAppleIDRequest *appleIdRequest = [appleIdProvider createRequest];
// 为了执行钥匙串凭证分享生成请求的一种机制
ASAuthorizationPasswordProvider *passwordProvider = [[ASAuthorizationPasswordProvider alloc] init];
ASAuthorizationPasswordRequest *passwordRequest = [passwordProvider createRequest];
NSMutableArray <ASAuthorizationRequest *>* requestArr = [NSMutableArray arrayWithCapacity:2];
if (appleIdRequest) {
[requestArr addObject:appleIdRequest];
}
if (passwordRequest) {
[requestArr addObject:passwordRequest];
}
// ASAuthorizationRequest:对于不同种类授权请求的基类
NSArray <ASAuthorizationRequest *>* requests = [requestArr copy];
// 由ASAuthorizationAppleIDProvider创建的授权请求 管理授权请求的控制器
ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:requests];
// 设置授权控制器通知授权请求的成功与失败的代理
authorizationController.delegate = self;
// 设置提供 展示上下文的代理,在这个上下文中 系统可以展示授权界面给用户
authorizationController.presentationContextProvider = self;
// 在控制器初始化期间启动授权流
[authorizationController performRequests];
}else{
// 处理不支持系统版本
if (self.block) {
self.block(@"该系统版本不可用Apple登录", NO, NO);
}
}
}
-
获取授权码
获取授权码需要在代码中实现两个代理回调ASAuthorizationControllerDelegate
、ASAuthorizationControllerPresentationContextProviding
分别用于处理授权登录成功和失败。#pragma mark - delegate //@optional 授权成功地回调 - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)){ // NSLog(@"授权完成:::%@", authorization.credential); // NSLog(@"%s", __FUNCTION__); // NSLog(@"%@", controller); // NSLog(@"%@", authorization); if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) { // 用户登录使用ASAuthorizationAppleIDCredential ASAuthorizationAppleIDCredential *appleIDCredential = (ASAuthorizationAppleIDCredential *)authorization.credential; NSString *user = appleIDCredential.user; // 使用钥匙串的方式保存用户的唯一信息 [SAMKeychain setPassword:user forService:[NSBundle mainBundle].bundleIdentifier account:ShareCurrentIdentifier]; // 使用过授权的,可能获取不到以下三个参数 NSString *familyName = appleIDCredential.fullName.familyName; NSString *givenName = appleIDCredential.fullName.givenName; NSString *email = appleIDCredential.email; NSData *identityToken = appleIDCredential.identityToken; NSData *authorizationCode = appleIDCredential.authorizationCode; // 服务器验证需要使用的参数 NSString *identityTokenStr = [[NSString alloc] initWithData:identityToken encoding:NSUTF8StringEncoding]; NSString *authorizationCodeStr = [[NSString alloc] initWithData:authorizationCode encoding:NSUTF8StringEncoding]; if (self.block) { self.block(user, YES, NO); } }else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]){ // 这个获取的是iCloud记录的账号密码,需要输入框支持iOS 12 记录账号密码的新特性,如果不支持,可以忽略 // Sign in using an existing iCloud Keychain credential. // 用户登录使用现有的密码凭证 ASPasswordCredential *passwordCredential = (ASPasswordCredential *)authorization.credential; // 密码凭证对象的用户标识 用户的唯一标识 NSString *user = passwordCredential.user; // 密码凭证对象的密码 NSString *password = passwordCredential.password; }else{ if (self.block) { self.block(@"授权信息均不符", NO, NO); } } } //MARK:授权失败的回调 - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)){ // Handle 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; default: break; } if (self.block) { self.block(errorMsg, NO, YES); } } //MARK ASAuthorizationControllerPresentationContextProviding //告诉代理应该在哪个window 展示内容给用户 - (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)){ // 返回window return [UIApplication sharedApplication].windows.lastObject; }
注意⚠️:完成上述后一般会返回需要的数据,如下
-
UserID
:Unique
,stable
,team-scoped user ID
,苹果用户唯一标识符,该值在同一个开发者账号下的所有App下是一样的,开发者可以用该唯一标识符与自己后台系统的账号体系绑定起来。 -
Verification data
:Identity token
,code
,验证数据,用于传给开发者后台服务器,然后开发者服务器再向苹果的身份验证服务端验证,本次授权登录请求数据的有效性和真实性 -
Account information
:Name
,verified email
,苹果用户信息,包括全名、邮箱等,注意:如果玩家登录时拒绝提供真实的邮箱账号,苹果会生成虚拟的邮箱账号,而且记录过的苹果账号再次登录这些参数拿不到。 - 验证,关于验证的这一步,需要传递授权码给自己的服务端,自己的服务端调用苹果
API
去校验授权码Generate and validate tokens
。
注意⚠️:验证一般是app端获取到信息,然后把authorizationCode
传给后台,验证地址,苹果返回id_token
,与客户端获取的identityToken值一样,格式如下
{
"access_token": "一个token",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "一个token",
"id_token": "结果是JWT,字符串形式,identityToken"
}
授权code
是有时效性的,且使用一次即失效
服务器拿到相应结果后,其中id_token
是JWT
数据,解码id_token
,得到如下内容
{
"iss":"https://appleid.apple.com",
"aud":"这个是你的app的bundle identifier",
"exp":1567482337,
"iat":1567481737,
"sub":"这个字段和客户端获取的user字段是完全一样的",
"c_hash":"8KDzfalU5kygg5zxXiX7dA",
"auth_time":1567481737
}
其中aud
与你app
的bundleID
一致,sub
就是授权用户的唯一标识,与手机端获得的user
一致,服务器端通过对比sub
字段信息是否与手机端上传的user
信息一致来确定是否成功登录
该token
的有效期是10
分钟。
网友评论