1.在开发者账号下勾选上Sign In With Apple,如下图所示:

2.项目配置如下:

3.实现逻辑

3.1导入头文件与创建apple登陆按钮
#import <AuthenticationServices/AuthenticationServices.h>
ASAuthorizationAppleIDButton *appleIDButton = [[ASAuthorizationAppleIDButton alloc] init];
appleIDButton.frame = CGRectMake(50, 100+CGRectGetHeight(self.view.frame) * 0.4, CGRectGetWidth(self.view.frame)-100, 50);
[appleIDButtonaddTarget:self action:@selector(appleIDButtonClicked) forControlEvents:UIControlEventTouchUpInside];
[self.viewaddSubview:appleIDButton];
3.2 app端申请授权登陆逻辑
//基于用户的Apple ID授权用户,生成用户授权请求的一种机制
ASAuthorizationAppleIDProvider *provide = [[ASAuthorizationAppleIDProvider alloc] init];
//创建新的AppleID 授权请求
ASAuthorizationAppleIDRequest *request = provide.createRequest;
//在用户授权期间请求的联系信息
request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
//由ASAuthorizationAppleIDProvider创建的授权请求 管理授权请求的控制器
ASAuthorizationController *controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
//设置授权控制器通知授权请求的成功与失败的代理
controller.delegate=self;
//设置提供 展示上下文的代理,在这个上下文中 系统可以展示授权界面给用户
controller.presentationContextProvider = self;
//在控制器初始化期间启动授权流
[controllerperformRequests];
3.3授权成功回调
#pragma mark --ASAuthorizationControllerDelegate
//授权成功的回调
//当授权成功后,我们可以通过这个拿到用户的 userID、email、fullName、authorizationCode、identityToken 以及 realUserStatus 等信息。
-(void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)) {
if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
// 用户登录使用ASAuthorizationAppleIDCredential
ASAuthorizationAppleIDCredential*credential = authorization.credential;
// NSString *state = credential.state;
// NSPersonNameComponents *fullName = credential.fullName;
// //苹果用户信息,邮箱
// NSString *email = credential.email;
//userId,authorizationCode,identityToken用于传给开发者后台服务器,然后开发者服务器再向苹果的身份验证服务端验证本次授权登录请求数据的有效性和真实性,详见https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens
//这个里面有详细的说明服务端如何验证客户端传过来的数据
//如果验证成功,可以根据 userIdentifier 判断账号是否已存在,若存在,则返回自己账号系统的登录态,若不存在,则创建一个新的账号,并返回对应的登录态给 App。
//苹果用户唯一标识符,该值在同一个开发者账号下的所有 App 下是一样的,开发者可以用该唯一标识符与自己后台系统的账号体系绑定起来。
NSString*userId = credential.user;
//授权码,这个是有一定的实效性的
NSString *authorizationCode = [[NSString alloc] initWithData:credential.authorizationCode encoding:NSUTF8StringEncoding]; // refresh token
//授权令牌,也就是JSON Web Token(JWT文件)
NSString *identityToken = [[NSString alloc] initWithData:credential.identityToken encoding:NSUTF8StringEncoding];
//用于判断当前登录的苹果账号是否是一个真实用户,取值有:unsupported、unknown、likelyReal。
ASUserDetectionStatusrealUserStatus = credential.realUserStatus;
// 需要使用钥匙串的方式保存用户的唯一信息
[HxKeyChainToolssaveData:userId];
_appleIDInfoTextView.text= [NSStringstringWithFormat:@"%@",credential];
}
3.4授权失败回调
//失败的回调
-(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;
}
3.5指定授权界面显示的window
#pragma mark - ASAuthorizationControllerPresentationContextProviding
//告诉代理应该在哪个window 展示授权界面给用户
-(ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)) {
return self.view.window;
}
3.6观察授权状态变化
//用户终止 App 中使用 Sign in with Apple 功能,用户在设置里注销了 AppleId
//这些情况下,App 需要获取到这些状态,然后做退出登录操作,或者重新登录。
//我们需要在 App 启动的时候,通过 getCredentialState:completion: 来获取当前用户的授权状态。
- (void)observeAuthticationState {
// 基于用户的Apple ID 生成授权用户请求的机制
ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];
// 注意 存储用户标识信息需要使用钥匙串来存储 这里使用NSUserDefaults 做的简单示例
NSString* userIdentifier = [HxKeyChainToolsgetUserID];
if(userIdentifier) {
// 在回调中返回用户的授权状态
[appleIDProvidergetCredentialStateForUserID:userIdentifiercompletion:^(ASAuthorizationAppleIDProviderCredentialStatecredentialState,NSError*_Nullableerror) {
// 苹果证书的授权状态
switch(credentialState) {
case ASAuthorizationAppleIDProviderCredentialRevoked:
// 苹果授权凭证失效
dispatch_async(dispatch_get_main_queue(), ^{
//做对应处理
});
break;
case ASAuthorizationAppleIDProviderCredentialAuthorized:
// 苹果授权凭证状态良好
dispatch_async(dispatch_get_main_queue(), ^{
//做对应处理
});
break;
case ASAuthorizationAppleIDProviderCredentialNotFound:
// 未发现苹果授权凭证
// 可以引导用户重新登录
dispatch_async(dispatch_get_main_queue(), ^{
//做对应处理
});
break;
default:
break;
}
}];
}
}
4.后台验证逻辑
4.1 IdentityToken说明
identityToken 是一个 Json Web Token (JWT).
它由点号 (".") 分割为三部分:
header
payload
signature
前两部分是两个 Json 字符串经过 base64Url 编码的结果。第三部分是前面二者加密后再做 base64Url 编码得到的。
identityToken 示例
eyJraWQiOiI4NkQ4OEtmIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLndhbmRhLndlYWx0aCIsImV4cCI6MTU5ODY5MzY5NywiaWF0IjoxNTk4NjkzMDk3LCJzdWIiOiIwMDA2OTkuNGU5YjdjYjEwMTY1NDFiOGJiMDI2MTY5MzFhMWVhYzUuMDgyOSIsImNfaGFzaCI6Ik0wUjFsTHVnaDg0QTNQWDBRblRkSUEiLCJlbWFpbCI6IjE1NTk5NjU4NjJAcXEuY29tIiwiZW1haWxfdmVyaWZpZWQiOiJ0cnVlIiwiYXV0aF90aW1lIjoxNTk4NjkzMDk3LCJub25jZV9zdXBwb3J0ZWQiOnRydWV9.US6LzO31vkLaRW_UjruIBvSmbRKWfoyk21xFeg-oQUbU9xYeKwliL7bOKyFxeh2MJx_mpcA0k6ypTtCGNbVhmvDPAT30Tu5XjZAgYpqGYaiQbmtoGSEIvHEybkyw3zvJwhSwL1VnISUvc2DZrIKX4NP0bKxpaZ29s9tgjso9gPak3gpQT80Gr3X_DsZ3gSDYlPw-AeuuNDnif5ArPC609CfUOJNH3AIN-QVa_VO3RAkpf2PmIIa23j4rSOI6VWbCkrHbKO2T50a3_93bcAP3q1Ajp-u7MclkHvWoeHDkRqKpqobvZkyTcEqf3NGR6PcXbZeGViZMBKiEh_sMCUNQAA
前两部分解码后结果示例:
header:
{
kid: "86D88Kf",
alg: "RS256"
}
payload:
{
iss: "https://appleid.apple.com",
aud: "com.wanda.wealth",
exp: 1598693697,
iat: 1598693097,
sub: "000699.4e9b7cb1016541b8bb02616931a1eac5.0829",
c_hash: "M0R1lLugh84A3PX0QnTdIA",
email: "1559965862@qq.com",
email_verified: "true",
auth_time: 1598693097,
nonce_supported: true
}.
字段名说明
iss 签发机构网址
aud bundle id
expint 过期时间戳
iat 签发时间
sub. user id
nouce. 客户端发出请求时携带的随机串,用于对照
c_hash 一段哈希,暂无用
email email
email_verified email 是否确认了
is_private_email 是否为 private email
auth_time 授权时间
分享一个解码工具 JWT Decoder
4.2服务端验证逻辑
第一种基于授权码实现验证
实现:客户端将收集到的authorizationCode(授权码只有五分钟的时效性)、identityToken(jwt文件)自己生成以及一些其他数据传给后台将这些数据传给apple的服务器进行验证,返回参数如下:

验证之后将结果返回给客户端进行登陆相关操作,服务端调用苹果的api地址
第二种基于jwt算法验证
实现:客户端只需要将identityToken(jwt文件)传给服务端,服务端可以通过identityToken中的header获取kid(密钥id)获取到公钥,然后通过公钥解密identityToken中的签名和identityToken中的payload是否一致从而实现验证。
因为苹果官网可以下载下来swift示例代码,这里给大家分享下oc实现的代码