Sign in with Apple已经很久了,之前只是看了一堆的文章理论,今天就实实在在的操作了一次,为后面项目中使用埋下基础。这篇文章会从头到尾描述清楚从客户端到服务器如何一步步的实现苹果登录。

1.几个官方资源

整体的流程如下:

交互流程

2.苹果后台操作

无论新建AppID还是老的AppID都需要配置支持Sign in with Apple

AppID Sign in with Apple

添加支持后,需要更新确认当前应用的描述文件支持Sign in with Apple

确认描述文件

项目设置支持Sign in with Apple

项目内设置支持

在Apple Developer Center添加供服务端使用的Keys

新建Keys

配置要使用Sign in with Apple的AppID

配置Sign in with Apple

生成完成后可以看到带有Key ID(服务端要用到)的一个key,只能下载一次!!!

生成Key

下载后的p8文件,后面验证的时候会用到

p8文件

3.代码开发(含服务端验证)

a.iOS端

系统提供了ASAuthorizationAppleIDButton的按钮可以直接使用,但也并没有强制使用,如果用户自定义切图的话,和官方提供的 样式最好保持相近。

//苹果登录的方法

-(void)loginWithAppleID

{

if (@available(iOS 13.0, *)) {

ASAuthorizationAppleIDProvider *provider = [[ASAuthorizationAppleIDProvider alloc] init];

ASAuthorizationAppleIDRequest *request = [provider createRequest];

request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];

ASAuthorizationController *vc = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];

vc.delegate = self;

vc.presentationContextProvider = self;

[vc performRequests];

} else {

// Fallback on earlier versions

}

}

#pragma mark - ASAuthorizationControllerPresentationContextProviding

- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0))

{

return self.view.window;

}

- (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;

NSString *userID = credential.user;

NSPersonNameComponents *fullName = credential.fullName;

NSString *email = credential.email;

NSString *authorizationCode = [[NSString alloc] initWithData:credential.authorizationCode encoding:NSUTF8StringEncoding]; // refresh token

NSString *identityToken = [[NSString alloc] initWithData:credential.identityToken encoding:NSUTF8StringEncoding]; // access token

ASUserDetectionStatus realUserStatus = credential.realUserStatus;

NSLog(@"state: %@", state);

NSLog(@"userID: %@", userID);

NSLog(@"fullName: %@", fullName);

NSLog(@"email: %@", email);

NSLog(@"authorizationCode: %@", authorizationCode);

NSLog(@"identityToken: %@ 长度:%ld", identityToken,(long)identityToken.length);

NSLog(@"realUserStatus: %@", @(realUserStatus));

//这里开始调用服务器的API进行登录

[self serververifyWithUserID:userID authorCode:authorizationCode token:identityToken];

}

}

- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0))

{

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;

}

NSLog(@"%@", errorMsg);

}

在代码中requestedScopes是用来获取用户信息的类型组,示例代码中获取了用户的名字和邮箱(用户可以选择隐藏邮箱,所以拿到的邮箱不一定是真的邮箱),在获取到用户的信息后调用后端API验证的时候,按照官方的描述,仅仅一个authorizationCode就可以了,在实际的开发中很多后端会让我们把userId、identityToken甚至BundleID也传递过去,方便他们的验证。可以看出,苹果的东西客户端在代码操作方面还是一如既往的方便!

由于是一个AppleID的第三方登录,可能会存在用户移除了授权情况,可以在应用内监听ASAuthorizationAppleIDProviderCredentialRevokedNotification方法进行数据的对比然后做对应的处理。

b.服务端

为了方便验证,我这里先自己作为服务器进行验证,向https://appleid.apple.com/auth/token请求需要的几个参数:

client_id:传递App的BundleID即可

code:传递客户端获取到的authorizationCode

grant_type:传递authorization_code固定字符串即可

client_secret:需要服务器自行计算

client_secret的计算方法:

其实是一个jwt的构建方法,下面列出一段Ruby的生成方法,让服务器按照参数自行生成一下即可:

require "jwt"

key_file = "xxxxx.p8" #从Developer Center后台下载的那个p8文件

team_id = "xxxxxx" #开发者账号的teamID

client_id = "com.xxx.xxx" #应用的BundleID

key_id = "xxxxxx" #从Developer Center后台找到keyid

validity_period = 180 #有效期 180天 测试的时候用 后端写的时候 让后端自己控制生成

private_key = OpenSSL::PKey::EC.new IO.read key_file

token = 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

执行后会获取一串字符串就是我们需要的client_secret字段。

#pragma mark - 验证服务

-(void)serververifyWithUserID:(NSString *)uid authorCode:(NSString *)code token:(NSString *)token

{

NSDictionary *dict1 = [self jwtDecodeWithJwtString:token];

NSLog(@">>解析原始的:%@",dict1);

NSDictionary *dict = @{@"client_id":@"com.sparkinglab.dsapp",@"code":code,@"grant_type":@"authorization_code",@"client_secret":@"eyJraWQiOiJURk41VTJYTks2IiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJLUjQzODRQV0haIiwiaWF0IjoxNTkzNDI2NzgxLCJleHAiOjE2MDg5Nzg3ODEsImF1ZCI6Imh0dHBzOi8vYXBwbGVpZC5hcHBsZS5jb20iLCJzdWIiOiJjb20uc3BhcmtpbmdsYWIuZHNhcHAifQ.PAEHDsq3tmO1bpSihnaIoAP-KOBePE7mw-U_jd6z8C1mut7jo-dyiNfnvNqzPMUXn-3pMAmoQRtj04wi632YYA"};

AFHTTPSessionManager *manager=[AFHTTPSessionManager manager];

[manager POST:@"https://appleid.apple.com/auth/token" parameters:dict progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {

NSLog(@"--success-->%@",responseObject);

NSDictionary *dict2 = [self jwtDecodeWithJwtString:[responseObject objectForKey:@"id_token"]];

NSLog(@">>解析请求到的:%@",dict2);

} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

NSLog(@"--error-->%@",error.localizedDescription);

}];

}

-(NSDictionary *)jwtDecodeWithJwtString:(NSString *)jwtStr {

NSArray * segments = [jwtStr componentsSeparatedByString:@"."];

NSString * base64String = [segments objectAtIndex:1];

int requiredLength = (int)(4 *ceil((float)[base64String length]/4.0));

int nbrPaddings = requiredLength - (int)[base64String length];

if(nbrPaddings > 0){

NSString * pading = [[NSString string] stringByPaddingToLength:nbrPaddings withString:@"=" startingAtIndex:0];

base64String = [base64String stringByAppendingString:pading];

}

base64String = [base64String stringByReplacingOccurrencesOfString:@"-" withString:@"+"];

base64String = [base64String stringByReplacingOccurrencesOfString:@"_" withString:@"/"];

NSData * decodeData = [[NSData alloc] initWithBase64EncodedString:base64String options:0];

NSString * decodeString = [[NSString alloc] initWithData:decodeData encoding:NSUTF8StringEncoding];

NSDictionary * jsonDict = [NSJSONSerialization JSONObjectWithData:[decodeString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];

return jsonDict;

}

客户端拿到的identityToken其实就是一个jwt,可以直接进行解码,就是图中的解析原始的,可以拿到用户的各种信息,sub就是userId,请求Apple服务器后返回的字段中id_token同样也是一个jwt,解析后也能拿到同样的信息,这就是为什么我在上面说给服务端一个authorizationCode就可以了,其余的信息通过Apple的服务器去验证并获取,基本上能请求通过就代表这用户的真实性了,有些服务端可能会根据客户端传递的userId和aud再进行一次二次比对验证。

请求打印

以上就是完整的Sign in with Apple的实现。

最新苹果服务器认证,Sign in with Apple-苹果登录(客户端和服务端)相关推荐

  1. mysql服务器是否支持tcp/ip连接,(3)MySQL客户端与服务端的TCP/IP及socket连接方式-Go语言中文社区...

    MySQL客户端与服务端的TCP/IP及socket连接方式 客户端与服务器模型 客户端与服务端模型 TCP/IP方式连接 解释说明 TCP/IP套接字方式是MySQL在任何平台下都提供的连接方式,也 ...

  2. 第一次安装使用rsync服务进行两台服务器之间的文件同步,并排查客户端到服务端网络不通的问题。

    下面为配置文件模板,rocky8没有这个配置文件,需要新建一个. [root@rocky8 ~]# vim /etc/rsyncd.confport=873log file=/var/log/rsyn ...

  3. https客户端与服务端认证过程详解

    Go和HTTPS 四月 30, 2015 27 条评论 近期在构思一个产品,考虑到安全性的原因,可能需要使用到HTTPS协议以及双向数字证书校验.之前只是粗浅接触过HTTP(使用Golang开 发微信 ...

  4. 长文详解!Docker客户端与服务端TLS认证(Docker Remote API认证)

    一.Docker Remote API的认证 在前一篇文章我们介绍了Docker Remote API如何使用:https://blog.csdn.net/qq_41453285/article/de ...

  5. java服务器向客户端发消息_java一个简单的客户端向服务端发送消息

    java一个简单的客户端向服务端发送消息 客户端代码: package com.chenghu.tcpip; import java.io.IOException; import java.io.Ou ...

  6. 在线登录注册功能(android客户端+javaweb服务端+腾讯云服务器+腾讯云数据库)

    在线登录注册功能(android客户端+javaweb服务端+腾讯云服务器+腾讯云数据库) 完整的项目已上传github仓库,链接在文章最下面 注:笔者在安卓客户端部分写了kotlin语言和java语 ...

  7. 网维大师系统虚拟盘添加副服务器,网维大师系统虚拟盘组件【副】服务端安装.doc...

    网维大师系统虚拟盘组件[副]服务端安装 网吧爱好者搜集 整理 http://www.pc0359.cn Ⅰ.在网维大师官方网站http://www.icafe8.com下载中心里,下载网维大师系统虚拟 ...

  8. 物联网系统上位机源码,含服务器和客户端 物联网服务端程序

    物联网系统上位机源码,含服务器和客户端 物联网服务端程序,可以接受市面上大多数透传数据的DTU登录,以及和DTU双向通讯 程序功能:能分组管理,不同的组别用户只可见自己组别的设备,设备和客户端登录掉线 ...

  9. 最新苹果服务器认证,iOS 苹果登录Sign in with Apple 和 服务端验证(nodejs 版)

    Sign in with Apple 服务端要做的其实很简单 nodejs需要装三个东西 npm install node-rsa npm install axios npm install json ...

最新文章

  1. 【转】Android设计中的.9.png
  2. HBase性能优化方法总结(1):配置优化
  3. 过滤某一个时间段的日志----sed
  4. git 忽略文件失效
  5. 【转】图形学基础之透视校正插值
  6. FreeMarker中文API手册(完整)
  7. Mysql5.X重点难点速记
  8. 任务管理平台_基于notion详谈任务规划的思路(二):搭建任务管理平台
  9. maven处理和java平级的资源文件
  10. 河南科技大学计算机信息安全技术考试,关于申报2020年信息安全等级保护项目的通知...
  11. oracle 查询dbid,查看oracle 数据库的DBID
  12. gin context和官方context_[系列文章] Gin框架 - 安装和路由配置
  13. GDB watch的使用
  14. html下拉表覆盖透明,css透明元素如何遮挡住fixed元素
  15. Java开发技术有哪些?
  16. 三朵云 华为_【创业前沿】华为突然传来大消息!对不起,我要辞职了!
  17. Redis 锁的实现方案
  18. Kotlin-三目表达式Kotlin版
  19. python获取股票_python根据股票代码获取当前数据
  20. 【入门笔记】量化投资是什么?

热门文章

  1. 海思平台项目经验总结
  2. 微信分享自定义图文链接
  3. 汉堡菜单html加logo,HTML+Sass实现HambergurMenu(汉堡包式菜单)
  4. 什么样的资金盘能活一年,还上了热搜?
  5. 区块链10年兴衰录:中国是最好的发展土壤
  6. 《股票魔法师》第3-5章读书分享
  7. 车联网:基于spark的车辆分析
  8. python猜年龄代码_Python实现猜年龄游戏代码实例
  9. 苹果/Mac电脑软件卸载不了怎么办?
  10. 判断点集与多边形的位置关系