在最新的审核指南中,出现了关于Sign In With Apple 的要求:

  • 4.8 Sign in with Apple

    Apps that use a third-party or social login service (such as Facebook Login, Google Sign-In, Sign in with Twitter, Sign In with LinkedIn, Login with Amazon, or WeChat Login) to set up or authenticate the user’s primary account with the app must also offer Sign in with Apple as an equivalent option. A user’s primary account is the account they establish with your app for the purposes of identifying themselves, signing in, and accessing your features and associated services.

    Sign in with Apple is not required if:

    • Your app exclusively uses your company’s own account setup and sign-in systems.
    • Your app is an education, enterprise, or business app that requires the user to sign in with an existing education or enterprise account.
    • Your app uses a government or industry-backed citizen identification system or electronic ID to authenticate users.
    • Your app is a client for a specific third-party service and users are required to sign in to their mail, social media, or other third-party account directly to access their content.

除了规定的几种特殊情况之外,凡是使用第三方授权登录或者社交账号授权登录功能的应用都必须提供使用apple账号进行授权登录的功能.目前来讲提供了方式触发apple账号授权:原生触发和web服务触发.无论是那种方式,都需要一些基本的准备.

准备工作

在开发工具Xcode添加支持

  • 打开项目工程,在TARGETS-->选中需要添加Sign In With Apple功能的项目;
  • 选中Signing & Capabilities子栏;
  • 点击 "+Capabilitiy",搜索"Sign In With Apple"并添加.  新版本的Xcode会自动将该特性同步到开发者网站后台,如果不放心可以登录开发者后台进行手动同步.

​​

获取移动端身份验证私钥

  • 登录开发者网站后台;
  • Certificates, Identifiers & Profiles主屏幕,从侧面导航中选择Keys;
  • 输入自定义字符串作为服务的标志,选择Sign in with Apple服务;

  • 点击Configure按钮,选择服务绑定的应用对应的bundle identifier;

  • 点击右上角"Save"按钮,继续点击右上角"Continue";

  • 点击右上角"Register";

  • 注册之后可获取到key ID,这个值会在生成访问密钥时会用到.你可以选择点击"Done"完成操作;也可以选择"Download"下载密钥.需要注意的是,这个密钥能下载且只能下载一次,如果丢失了就只能按照重新生成.下载之后会得到一个后缀为p8的密钥文件,请妥善保存.
  • 如果你想要进行移动校验测试,将如下脚本保存为secret_gen.rb,执行ruby secret_gen.rb获取到密钥client_secret.如果对移动端测试没有兴趣,直接将.p8文件发给后台就完事了.
require "jwt"key_file = ""//刚才下载的.p8文件的路径
team_id = ""//TeamID,在开发者后台可以获取
client_id = "" //应用的bundleId
key_id = "" //刚才获取到的key ID
validity_period = 180 # In days. Max 180 (6 months) according to Apple docs.private_key = OpenSSL::PKey::EC.new IO.read key_filetoken = JWT.encode({iss: team_id,iat: Time.now.to_i,exp: Time.now.to_i + 86400 * validity_period,aud: "https://appleid.apple.com",sub: client_id},private_key,"ES256",header_fields={kid: key_id }
)
puts token

申请web端服务

这部分主要用于通过加载链接的方式唤起appld账号登录授权,如果不需要实现这部分支持可以忽略.

  • 登录开发者网站后台;
  • Certificates, Identifiers & Profiles主屏幕,从侧面导航中选择Identifiers;
  • 点击上方添加按钮,选择 "Services IDs"服务, 点击"Continue";

  • 填写描述字符串(Description)和标记字符串(Identifier), 点击"Continue",在下一步中点击"Register".
  • 重新进入创建的Service ID服务,选择Sign In With Apple服务;

  • 进行"Configure"进行配置,选择绑定服务的应用,并填写Domains and Subdomains以及Return URLs, 域名和回调链接可以填写多个在进行授权时选择其中一个中作为redirect_uri可以了,然后点击"Done";
  • 选择"Continue",并进行"Save".

原生触发登录授权

主要是依靠原生拉起授权界面供用户登录交互,这部分实现官方给出了示例代码,可供参考.以下实现中使用OC代码做说明,首先需要实现代理方法:

@interface LoginViewController ()<ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding>
@property (weak, nonatomic) IBOutlet UIStackView *loginProviderStackView;@end//用户展示弹窗的父window
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller {return self.view.window;
}/// - Tag: did_complete_authorization
-(void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)){if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {ASAuthorizationAppleIDCredential * credential = authorization.credential;NSString *state = credential.state; //是ASAuthorizationRequest对象传递过来的值NSString * userIdentifier = credential.user; //apple系统的用户标识NSPersonNameComponents *fullName = credential.fullName;NSString * email = credential.email;//refresh tokenNSString * authorizationCode = [[NSString alloc]initWithData:credential.authorizationCode encoding:NSUTF8StringEncoding];// access tokenNSString * identityToken = [[NSString alloc]initWithData:credential.identityToken encoding:NSUTF8StringEncoding];ASUserDetectionStatus realUserStatus = credential.realUserStatus;NSLog(@"state: %@", state);NSLog(@"userID: %@", userIdentifier);NSLog(@"fullName: %@", fullName);NSLog(@"email: %@", email);NSLog(@"authorizationCode: %@", authorizationCode);NSLog(@"identityToken: %@", identityToken);NSLog(@"realUserStatus: %@", @(realUserStatus));/*https://appleid.apple.com/auth/token需要四个参数client_id(Required): 即应用的bundleIdclient_secret(Required): 使用.p8文件生成的密钥client_secretgrant_type(Required): authorization_codecode:authorizationCode*/} else if([authorization.credential isKindOfClass:[ASPasswordCredential class]]) {//需要将credential保存在iCloud keychain中ASPasswordCredential *passwordCredential = (ASPasswordCredential *)authorization.credential;// Sign in using an existing iCloud Keychain credential.NSString *username = passwordCredential.user;NSString *password = passwordCredential.password;NSLog(@"username == %@, password == %@", username, password);}
}
/// - Tag: did_complete_error
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)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;}}

在官方给出的实现代码中,分为两部分:

  • 第一次授权登录:
- (void)viewDidLoad {[super viewDidLoad];if (@available(iOS 13.0, *)) {[self setupProviderLoginView];}// Do any additional setup after loading the view.
}
- (void)setupProviderLoginView {ASAuthorizationAppleIDButton *authorizationButton = [ASAuthorizationAppleIDButton new];[authorizationButton addTarget:self action:@selector(handleAuthorizationAppleIDButtonPress) forControlEvents:UIControlEventTouchUpInside];[self.loginProviderStackView addArrangedSubview:authorizationButton];
}
- (void)handleAuthorizationAppleIDButtonPress {ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];ASAuthorizationAppleIDRequest *request = [appleIDProvider createRequest];request.state = @"handleAuthorizationAppleIDButtonPress"; //会传递值到带来方法中request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];authorizationController.delegate = self;authorizationController.presentationContextProvider = self;[authorizationController performRequests];}

如果出现了

[core] Authorization failed: Error Domain=AKAuthenticationError Code=-7026 "(null)" UserInfo={AKClientBundleID=com.xxxx.xxxx}

就说明项目中未添加对Sign In With Apple功能的支持,在TARGETS->项目-->Signing & Capabilities --> +Capability中进行添加即可.

  • 之前已经进行过登录授权
- (void)viewDidAppear:(BOOL)animated {[super viewDidAppear:animated];if (@available(iOS 13.0, *)) {
//这个方法只有在登录授权成功一次之后,且授权没有被取消没有被移除(Revoke)时才会起到作用弹出面容或者指纹识别登录,否则该方法没有任何作用[self performExistingAccountSetupFlows];}
}
- (void)performExistingAccountSetupFlows {// Prepare requests for both Apple ID and password providers.NSArray<ASAuthorizationRequest *> *requests = @[[[ASAuthorizationAppleIDProvider new] createRequest],[[ASAuthorizationPasswordProvider new] createRequest]];// Create an authorization controller with the given requests.ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:requests];authorizationController.delegate = self;authorizationController.presentationContextProvider = self;[authorizationController performRequests];}

web触发登录授权

在这部分实现中允许应用通过加载URL的方式获取apple账号登录授权,需要在开发者后台创建Service ID, 然后在Return URLs中填入可用的链接URL作为回调地址redirect_uri.例如使用https://www.baidu.com,可以在浏览器中加载如下链接触发apple账号登录授权:

https://appleid.apple.com/auth/authorize?response_type=code&redirect_uri=https%3a%2f%2fwww.baidu.com&client_id=`service ID 对应的bundleID`

需要注意的是redirect_uri参数对应的回调地址需要进行encode编码,然后就可以正常触发apple账号登录服务,在完整授权之后,web界面会加载回调链接并将授权code作为参数拼接在回调地址之后供验证使用:

https://www.baidu.com/?code=xxxx.0.nrtzw.yyyy

这样web页面就可以通过获取query中的code参数获取到授权码进行验证.

授权验证

在原生的授权中,验证可以分为两步骤.

  • 移动端验证:如果授权成功,在ASAuthorizationControllerDelegate代理方法authorizationController:didCompleteWithAuthorization:中可以获取到authorizationCode和identityToken两个参数
NSData *dataWithBase64String(NSString *payload) {NSData *data = nil;unsigned char *decodedBytes = NULL;@try {
#define __ 255static char decodingTable[256] = {__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__,  // 0x00 - 0x0F__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__,  // 0x10 - 0x1F__,__,__,__, __,__,__,__, __,__,__,62, __,__,__,63,  // 0x20 - 0x2F52,53,54,55, 56,57,58,59, 60,61,__,__, __, 0,__,__,  // 0x30 - 0x3F__, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,  // 0x40 - 0x4F15,16,17,18, 19,20,21,22, 23,24,25,__, __,__,__,__,  // 0x50 - 0x5F__,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,  // 0x60 - 0x6F41,42,43,44, 45,46,47,48, 49,50,51,__, __,__,__,__,  // 0x70 - 0x7F__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__,  // 0x80 - 0x8F__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__,  // 0x90 - 0x9F__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__,  // 0xA0 - 0xAF__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__,  // 0xB0 - 0xBF__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__,  // 0xC0 - 0xCF__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__,  // 0xD0 - 0xDF__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__,  // 0xE0 - 0xEF__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__,  // 0xF0 - 0xFF};encoding = [encoding stringByReplacingOccurrencesOfString:@"=" withString:@""];NSData *encodedData = [encoding dataUsingEncoding:NSASCIIStringEncoding];unsigned char *encodedBytes = (unsigned char *)[encodedData bytes];NSUInteger encodedLength = [encodedData length];if( encodedLength >= (NSUIntegerMax - 3) ) return nil; // NSUInteger overflow checkNSUInteger encodedBlocks = (encodedLength+3) >> 2;NSUInteger expectedDataLength = encodedBlocks * 3;unsigned char decodingBlock[4];decodedBytes = malloc(expectedDataLength);if( decodedBytes != NULL ) {NSUInteger i = 0;NSUInteger j = 0;NSUInteger k = 0;unsigned char c;while( i < encodedLength ) {c = decodingTable[encodedBytes[i]];i++;if( c != __ ) {decodingBlock[j] = c;j++;if( j == 4 ) {decodedBytes[k] = (decodingBlock[0] << 2) | (decodingBlock[1] >> 4);                decodedBytes[k+1] = (decodingBlock[1] << 4) | (decodingBlock[2] >> 2);decodedBytes[k+2] = (decodingBlock[2] << 6) | (decodingBlock[3]);j = 0;k += 3;}}}// Process left over bytes, if anyif( j == 3 ) {decodedBytes[k] = (decodingBlock[0] << 2) | (decodingBlock[1] >> 4);                decodedBytes[k+1] = (decodingBlock[1] << 4) | (decodingBlock[2] >> 2);k += 2;} else if( j == 2 ) {decodedBytes[k] = (decodingBlock[0] << 2) | (decodingBlock[1] >> 4);                k += 1;}data = [[NSData alloc] initWithBytes:decodedBytes length:k];}}@catch (NSException *exception) {data = nil;NSLog(@"WARNING: error occured while decoding base 32 string: %@", exception);}@finally {if( decodedBytes != NULL ) {free( decodedBytes );}}return data;
}NSString * authorizationCode = [[NSString alloc]initWithData:credential.authorizationCode encoding:NSUTF8StringEncoding];
NSString * identityToken = [[NSString alloc]initWithData:credential.identityToken encoding:NSUTF8StringEncoding];
NSArray <NSString *> *JWTResult = [identityToken componentsSeparatedByString:@"."];
if (JWTResult.count == 3) {NSString *payload = [JWTResult objectAtIndex:1];NSError *error = nil;NSDictionary *content =  [NSJSONSerialization JSONObjectWithData: dataWithBase64String(payload) options:(0) error:&error];NSString *sub = content[@"sub"];NSLog(@"content == %@, sub == %@", content, sub);NSAssert([sub isEqualToString:userID], @"userIdentifier[sign in with apple]验证不通过");}

根据JWT的验证规则,取出payload中的sub参数部分,如果和userIdentifier保持一致,则第一步验证通过;否则验证失败.

  • apple后台验证:验证有两种方式,可以使用验证authorization_code或者验证identityToken

    • 验证authorization_code,apple提供了后台验证的接口供签权验证
POST https://appleid.apple.com/auth/token
Content-Type: application/x-www-form-urlencoded{ client_id: 即signInWithApple服务所在应用对应的bundleIdclient_secret: 生成的密钥 //使用ruby secret_gen.rb生成的密钥grant_type: authorization_code code:`authorizationCode`
}

然后既可以获取到结果,大概长这个样子:

{"iss": "https://appleid.apple.com","aud": "这个对应app的bundleid","exp": 1567494694,"iat": 1567494094,"sub": "这个字段和手机端获取的user信息相同","c_hash": "nRYP2wGXBGT0bIYWibx4Yg","auth_time": 1567494094
}

校验sub字段是否与原始获取到的useridentifier一致即可.需要注意的是authorizationCode只能使用一次而且有时间限制,如果已经使用进行过authorizationCode进行验证或者超出有效期,验证会失败:

{"error": "invalid_grant"
}
  • 验证identityToken

    • 需要逆向构造过程,decode出JWT的三个部分
    • 从https://appleid.apple.com/auth/keys中获取公钥,并将公钥转换为pem对JWT进行验证
    • identityToken通过验证,则可以根据其payload中的内容进行验证等操作

因为idnetityToken使用非对称加密 RSASSA【RSA签名算法】 和 ECDSA【椭圆曲线数据签名算法】,当验证签名的时候,利用公钥来解密Singature,当解密内容与base64UrlEncode(header) + "." + base64UrlEncode(payload) 的内容完全一样的时候,表示验证通过。

Sign In With Apple相关推荐

  1. ios 登录 java 后台,IOS苹果登录sign in with apple后端校验

    IOS苹果登录sign in with apple后端校验 最近新开发的app在IOS平台app store connent提审的时候,被拒了,原因是app上如果有接第三方登陆(比如微信,微博,fac ...

  2. Apple Sign in with Apple(苹果授权登录PHP)

    Apple Sign in with Apple(苹果授权登录PHP) 文章目录 Apple Sign in with Apple(苹果授权登录PHP) 一.登录Apple Developer 二.创 ...

  3. sign in with Apple,使用Apple授权登录

    软件环境:Xcode 11.4.iOS13+ 创建时间:2020年 03月15号 更新时间:2021年 03月02号 这篇文章都说了什么 使用Apple登录的注意事项 接入原理概览 客户端编码 审核规 ...

  4. Sign in with Apple (通过Apple 登录)

    在 WWDC 2019 上,苹果推出了自家的 Sign in with Apple 功能,这很 Apple.可能苹果看到第三方登录百家争鸣,琢磨着自己也搞了个,这对很多第三方登录来说可能是个威胁. 使 ...

  5. uni-app,原生APP,关于苹果APP集成Sign in with Apple(通过Apple登录)后,APP内注册需要强制绑定手机号,审核被拒问题

    在提交审核页面,备注里写上如下内容,即可通过. 你好,Apple! 因为我们的APP包含发贴和发布评论功能,根据中国网信办相关规定,对信息发布者要求后台实名.具体规定如下: <互联网用户公众账号 ...

  6. 【iOS】苹果登录Sign in with Apple

    在iOS13中,如果苹果开发者提供任何其他第三方登录,就必须提供"苹果登录"选项.也就是说,如果软件要求"微信登录"或是"QQ登录"时,必须 ...

  7. Sign in With Apple (苹果授权登录)

    Sign in With Apple (苹果授权登录) 关于Sign in With Apple (苹果授权登录)的问题,公司app上架appStore被拒原因是使用第三方授权登陆但是却没有使用苹果账 ...

  8. 苹果授权登录Sign In With Apple亲测通过版[100%成功]

    苹果授权登录Sign In With Apple后台代码实现JAVA版本亲测通过版 废话不多说,直接复制把自己的包名写上就可以用了 有个别的小坑,HttpUtil自己写,没附上 Base64一定要用o ...

  9. iOS13 sign in with Apple客户端接入遇到的一些问题

    公司项目需要接入sign in with Apple,花了几天时间,查看各种文档博客,最终完成接入. 客户端接入遇到的一些问题 1.首次登录时调用的代码: if (@available(iOS 13. ...

  10. 【iOS】Sign in with Apple

    [环境]Xcode 12.0.1,macOS 10.15.7 一.ViewController 代码 1.创建一个新工程,取名为 SignInWithApple,设置为自动签名 2.为其添加 Sign ...

最新文章

  1. ceph存储引擎bluestore解析
  2. 01:初识Redis
  3. 高级软件工程第七次作业:东理三剑客团队作业-随笔6
  4. 类oracle数据库pss,Oracle笔记
  5. Image、Byte[]、Bitmap相互转换
  6. linux系统管理试卷必修B卷,2013-2014Linux系统管理试卷
  7. 怎么用git将本地代码上传到远程服务器_git命令 将本地代码上传到远程服务器...
  8. 解决DataGridView绑定List后不能排序的问题
  9. mysql的存储过程放在哪里_mysql存储过程求解,错误在哪里?
  10. 索引体积_米家温湿度计体积虽小,耗电不小,如果经常离线,换颗电池吧
  11. c++ string截取字符串_String类的常见用法
  12. 【Java基本功】一文读懂final关键字的用法
  13. swift语言 数组定义_如何在Swift中声明弱引用数组?
  14. C语言库函数查找工具MSDN
  15. 火狐浏览器怎么支持html5,支持HTML5/CSS技术火狐浏览器8
  16. linux kill全部进程,linux进程管理及kill命令详解
  17. 【推理加速】博客翻译:利用融合conv和bn的方法加速模型
  18. Related Problems
  19. 打印机连接计算机用什么端口,如何设置打印机端口,打印机端口设置的方法步骤...
  20. excel中替换功能的新颖用法

热门文章

  1. linux raw图片处理软件,Kandao Raw+下载-Raw图片处理软件 v1.1.2.1 官方版 - 下载吧
  2. Android蓝牙问题
  3. 使用Charles监控手机应用的网络请求
  4. 德国:一个中国人在德国生活的真实感受
  5. 阿桑奇:维基解密将把CIA网络攻击工具资料提供给科技公司
  6. 开源一个极简的群日程工具
  7. 前端三剑客 Html Css JavaScript
  8. 关于深圳市住房和建设局《关于建立二手住房成交参考价格发布机制的通知》涉嫌法律依据不足、越权行使银保监人行市监等部门职能、涉嫌限制《物权法》赋予公民合法权益、涉嫌违背国务院及国家发改委上位法规
  9. 华为nova9系列新品发布:开启Vlog拍摄新玩法
  10. 希尔排序----附图解(C语言)