第一步:导入库文件
#import <AuthenticationServices/AuthenticationServices.h>
第二步:绘制登录按钮,也可以自定义
-(void)setupUI{
if (@available(iOS 13.0, *)) {
// Sign In With Apple Button
ASAuthorizationAppleIDButton *appleIDButton = [ASAuthorizationAppleIDButton new];
appleIDButton.frame = CGRectMake(.0, .0, CGRectGetWidth(self.view.frame) - 40.0, 100.0);
CGPoint origin = CGPointMake(20.0, CGRectGetMidY(self.view.frame));
CGRect frame = appleIDButton.frame;
frame.origin = origin;
appleIDButton.frame = frame;
appleIDButton.cornerRadius = CGRectGetHeight(appleIDButton.frame) * 0.25;
[self.view addSubview:appleIDButton];
[appleIDButton addTarget:self action:@selector(handleAuthrization:) forControlEvents:UIControlEventTouchUpInside];
}
}
第三步:发起授权
//! 处理授权
- (void)handleAuthrization:(UIButton *)sender {
if (@available(iOS 13.0, *)) {
// 基于用户的Apple ID授权用户,生成用户授权请求的一种机制
ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];
// 创建新的AppleID 授权请求
ASAuthorizationAppleIDRequest *request = appleIDProvider.createRequest;
// 在用户授权期间请求的联系信息
request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
/*
// 为了执行钥匙串凭证分享生成请求的一种机制,暂时好像没啥用
ASAuthorizationPasswordProvider *passwordProvider = [[ASAuthorizationPasswordProvider alloc] init];
ASAuthorizationPasswordRequest *passwordRequest = [passwordProvider createRequest];
*/
// 由ASAuthorizationAppleIDProvider创建的授权请求 管理授权请求的控制器
ASAuthorizationController *controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
// 设置授权控制器通知授权请求的成功与失败的代理
controller.delegate = self;
// 设置提供 展示上下文的代理,在这个上下文中 系统可以展示授权界面给用户
controller.presentationContextProvider = self;
// 在控制器初始化期间启动授权流
[controller performRequests];
}
}
第四步:授权结果处理
#pragma mark - Delegate
//! 授权成功地回调
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)){
NSLog(@"authorization.credential:%@", authorization.credential);
NSMutableString *mStr = [NSMutableString string];
if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
// 用户登录使用ASAuthorizationAppleIDCredential
ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential;
///凭证信息
//用户唯一ID
NSString *user = appleIDCredential.user;
//授权验证信息
NSData *token = appleIDCredential.identityToken;
//用户名,邮箱信息(只有第一次授权才会带回来,以后授权成功都是返回空)
NSString *familyName = appleIDCredential.fullName.familyName;
NSString *givenName = appleIDCredential.fullName.givenName;
NSString *email = appleIDCredential.email;
} else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]) {
// 这个获取的是iCloud记录的账号密码,需要输入框支持iOS 12 记录账号密码的新特性
//这个回调我是没有测试出来过,不知道怎么搞
// 用户登录使用现有的密码凭证
ASPasswordCredential *passwordCredential = authorization.credential;
// 密码凭证对象的用户标识 用户的唯一标识
NSString *user = passwordCredential.user;
// 注意 存储用户标识信息需要使用钥匙串来存储 这里笔者简单期间 使用NSUserDefaults 做的简单示例
[[NSUserDefaults standardUserDefaults] setObject:user forKey:@"userIdentifier"];
// 密码凭证对象的密码
NSString *password = passwordCredential.password;
} else {
NSLog(@"授权信息均不符");
}
}
//! 授权失败的回调
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)){
NSLog(@"%s", __FUNCTION__);
NSLog(@"错误信息:%@", 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;
}
NSLog(@"error:%@", errorMsg);
}
//告诉代理应该在哪个window 展示内容给用户
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)){
NSLog(@"调用展示window方法:%s", __FUNCTION__);
// 返回window
return self.view.window;
}
在授权登录成功回调中,我们可以拿到以下几类数据
-
UserID:
Unique, stable, team-scoped user ID
,苹果用户唯一标识符,该值在同一个开发者账号下的所有App
下是一样的,开发者可以用该唯一标识符与自己后台系统的账号体系绑定起来(这与国内的微信、QQ、微博
等第三方登录流程基本一致) -
Verification data:
Identity token, code
,验证数据,用于传给开发者后台服务器,然后开发者服务器再向苹果的身份验证服务端验证,本次授权登录请求数据的有效性和真实性,详见Sign In with Apple REST API -
Account information:
Name, verified email
,苹果用户信息,包括全名、邮箱等,注意:如果玩家登录时拒绝提供真实的邮箱账号,苹果会生成虚拟的邮箱账号,而且记录过的苹果账号再次登录这些参数拿不到
验证
关于验证的这一步,需要传递授权码给自己的服务端,自己的服务端调用苹果API
去校验授权码Generate and validate tokens。如果验证成功,可以根据userIdentifier
判断账号是否已存在,若存在,则返回自己账号系统的登录态,若不存在,则创建一个新的账号,并返回对应的登录状态给App
- 推荐验证步骤为:
- 服务端拿
authorizationCode
去苹果后台验证,验证地址https://appleid.apple.com/auth/token
,苹果返回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
分钟,具体后端验证参考附录
第五步:已授权状态获取与监听
1.获取已授权用户当前状态
- (void)authorizationCredentialState{
if (@available(iOS 13.0, *)) {
// 基于用户的Apple ID 生成授权用户请求的机制
ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];
// 注意 存储用户标识信息需要使用钥匙串来存储 这里笔者简单期间 使用NSUserDefaults 做的简单示例
NSString *userIdentifier = [[NSUserDefaults standardUserDefaults] objectForKey:@"userIdentifier"];
if (userIdentifier) {
__block NSString *errorMsg = nil;
//Returns the credential state for the given user in a completion handler.
// 在回调中返回用户的授权状态
[appleIDProvider getCredentialStateForUserID:userIdentifier completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError * _Nullable error) {
switch (credentialState) {
// 苹果证书的授权状态
case ASAuthorizationAppleIDProviderCredentialRevoked:
// 苹果授权凭证失效,退出登录,重新授权
errorMsg = @"苹果授权凭证失效";
break;
case ASAuthorizationAppleIDProviderCredentialAuthorized:
// 苹果授权凭证状态良好
errorMsg = @"苹果授权凭证状态良好";
break;
case ASAuthorizationAppleIDProviderCredentialNotFound:
// 未发现苹果授权凭证,退出登录,重新授权
errorMsg = @"未发现苹果授权凭证";
break;
// 可以引导用户重新登录
case ASAuthorizationAppleIDProviderCredentialTransferred:
errorMsg = @"苹果授权信息变动";
break;
}
}];
}
}
}
2.使用通知的方式监听授权状态变化
//! 添加苹果登录的状态通知
- (void)observeAppleSignInState {
if (@available(iOS 13.0, *)) {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(handleSignInWithAppleStateChanged:) name:ASAuthorizationAppleIDProviderCredentialRevokedNotification object:nil];
}
}
//! 观察SignInWithApple状态改变
- (void)handleSignInWithAppleStateChanged:(id)noti {
NSLog(@"%s", __FUNCTION__);
NSLog(@"%@", noti);
}
- (void)dealloc {
if (@available(iOS 13.0, *)) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:ASAuthorizationAppleIDProviderCredentialRevokedNotification object:nil];
}
}
参考:https://www.jianshu.com/p/e1284bd8c72a
附:官方示例代码 Swift 版
附:What the Heck is Sign In with Apple?
附:Sign In with Apple 从登陆到服务器验证
附:苹果授权登陆后端验证
附:[官方文档] Generate and validate tokens
附:[官方文档] App Store审核指南
附:SignInAppleDemo