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

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

1.几个官方资源

整体的流程如下:

2156b3767b86

交互流程

2.苹果后台操作

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

2156b3767b86

AppID Sign in with Apple

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

2156b3767b86

确认描述文件

项目设置支持Sign in with Apple

2156b3767b86

项目内设置支持

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

2156b3767b86

新建Keys

配置要使用Sign in with Apple的AppID

2156b3767b86

配置Sign in with Apple

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

2156b3767b86

生成Key

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

2156b3767b86

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再进行一次二次比对验证。

2156b3767b86

请求打印

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

参与评论 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:1024 设计师:我叫白小胖 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值