iOS13 中,如果 App 提供第三方登录,就必须添加 苹果登录 Sign in with Apple 选项,并要求所有开发者于 2020年4月之前 完成现有应用的更新,否则审核不给通过。

iOS 苹果授权登录(Sign in with Apple)系列之Apple Developer配置篇

iOS 苹果授权登录(Sign in with Apple)系列之原生篇

iOS 苹果授权登录(Sign in with Apple)系列之uniapp篇

iOS 苹果授权登录(Sign in with Apple)系列之服务端篇

创建iOS项目

开启 苹果授权登录(Sign in with Apple)功能

1、在 Xcode 里的 Signing & Capabilities 开启 Sign in with Apple 功能

2、添加完 Sign In With Apple

Sign In with Apple 按钮集成

Apple 提供了2种方式来集成 Sign In with Apple

使用 Apple 提供的按钮样式

官方提供了一个 ASAuthorizationAppleIDButton (继承自UIControl),使用这个来创建一个登录按钮。
按钮文字大小会随着按钮宽高的变化放大或缩小。

// 导入头文件
#import <AuthenticationServices/AuthenticationServices.h>

ASAuthorizationAppleIDButton * appleIDBtn = [ASAuthorizationAppleIDButton buttonWithType:ASAuthorizationAppleIDButtonTypeDefault style:ASAuthorizationAppleIDButtonStyleWhite];

appleIDBtn.frame = CGRectMake(30, 80, self.view.bounds.size.width - 60, 64);

[appleIDBtn addTarget:self action:@selector(didAppleIDBtnClicked) forControlEvents:UIControlEventTouchUpInside];

[self.view addSubview:appleIDBtn];

Apple 提供的按钮有3种类型,3种文字显示效果。

// 按钮文案类型
typedef NS_ENUM(NSInteger, ASAuthorizationAppleIDButtonType) {
    ASAuthorizationAppleIDButtonTypeSignIn,
    ASAuthorizationAppleIDButtonTypeContinue,

    ASAuthorizationAppleIDButtonTypeSignUp API_AVAILABLE(ios(13.2), macos(10.15.1), tvos(13.1)) API_UNAVAILABLE(watchos),

    ASAuthorizationAppleIDButtonTypeDefault = ASAuthorizationAppleIDButtonTypeSignIn,
} NS_SWIFT_NAME(ASAuthorizationAppleIDButton.ButtonType) API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0)) API_UNAVAILABLE(watchos);
// 按钮风格
typedef NS_ENUM(NSInteger, ASAuthorizationAppleIDButtonStyle) {
    ASAuthorizationAppleIDButtonStyleWhite,
    ASAuthorizationAppleIDButtonStyleWhiteOutline,
    ASAuthorizationAppleIDButtonStyleBlack,
} NS_SWIFT_NAME(ASAuthorizationAppleIDButton.Style) API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0)) API_UNAVAILABLE(watchos);

对于 ASAuthorizationAppleIDButton 我们能够自定义的东西比较少,比如不能修改背景色,文案只有三种可以选择,可以调整的只有圆角 cornerRadius 和尺寸 size

使用自定义按钮样式

对于自定义按钮,凭你开心咯,想怎么自定义就怎么自定义。

UIButton * signBtn = [UIButton buttonWithType:UIButtonTypeCustom];

signBtn.frame = CGRectMake(30, 600, self.view.bounds.size.width - 60, 40);

signBtn.backgroundColor = [UIColor orangeColor];

[signBtn setTitle:@"Sign in with Apple" forState:UIControlStateNormal];

[signBtn addTarget:self action:@selector(didCustomBtnClicked) forControlEvents:UIControlEventTouchUpInside];

[self.view addSubview:signBtn];
国际化配置(针对 ASAuthorizationAppleIDButton )

不知道发现没有 ASAuthorizationAppleIDButton 的按钮文字都是英文,那如果我想要显示中文呢?

按照下图配置添加 Chinese, Simplified 当机子语言为中文时就会显示中文

虽然是改为了中文,但是还是不能自定义文字哦

Sign In with Apple 发起授权登录

1、发起授权代码

Apple 还把 iCloud KeyChain password 集成到了这套 API 里,我们在使用的时候,只需要多创建一个 ASAuthorizationPasswordRequest 这样如果 KeyChain 里面也有登录信息的话,可以直接使用里面保存的用户名和密码进行登录。

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

    ASAuthorizationAppleIDProvider * appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];
    ASAuthorizationAppleIDRequest * authAppleIDRequest = [appleIDProvider createRequest];
    ASAuthorizationPasswordRequest * passwordRequest = [[[ASAuthorizationPasswordProvider alloc] init] createRequest];

    NSMutableArray <ASAuthorizationRequest *> * array = [NSMutableArray arrayWithCapacity:2];
    if (authAppleIDRequest) {
        [array addObject:authAppleIDRequest];
    }
    if (passwordRequest) {
        [array addObject:passwordRequest];
    }
    NSArray <ASAuthorizationRequest *> * requests = [array copy];

    ASAuthorizationController * authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:requests];
    authorizationController.delegate = self;
    authorizationController.presentationContextProvider = self;
    [authorizationController performRequests];

} else {
    // 处理不支持系统版本
    NSLog(@"系统不支持Apple登录");
}
#pragma mark- ASAuthorizationControllerDelegate
// 授权成功
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization {
}

// 授权失败
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error {
}

ASAuthorizationControllerPresentationContextProviding 主要是告诉 ASAuthorizationController 在哪个 window 上显示。

#pragma mark- ASAuthorizationControllerPresentationContextProviding
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller {
    return self.view.window;
}

高能预警: 慎用 ASAuthorizationPasswordRequest

当启用 ASAuthorizationPasswordRequest停止使用 Apple ID(真机-设置-账户-密码与安全性-使用您 Apple ID 的 App-App列表-停止使用 Apple ID),如果 KeyChain 里面没有登录信息且重新使用 苹果授权登录(Sign in with Apple) 会报未知错误

2019-12-24 10:20:20.410856+0800 SignInWithApple[2759:550203] [core] Authorization failed: Error Domain=AKAuthenticationError Code=-7001 "(null)" UserInfo={AKClientBundleID=com.wangquanwei.SignInWithApple}
2019-12-24 10:20:20.411130+0800 SignInWithApple[2759:550014] 授权请求失败未知原因

2、用户发起授权后,系统就会弹出登录验证的弹窗。

如果用户没有同意授权或者用户取消授权,点击登录的时候,会显示这个授权弹窗。在这个授权页面,我们可以修改自己的姓名,以及可以选择共享我的电子邮箱或者隐藏邮件地址。这样一来,就可以达到隐藏自己真实信息的目的。

在成功授权后再次请求授权,会显示以下界面

3、授权成功回调

高能预警
高能预警
高能预警
授权信息里的用户信息(email、NSPersonNameComponents对象所有属性)当且仅当第一次授权时才会返回,之后就算 停止使用 Apple ID 再重新授权都不会返回用户信息

// 授权成功
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)) {

    if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {

        ASAuthorizationAppleIDCredential * credential = authorization.credential;

        // 苹果用户唯一标识符,该值在同一个开发者账号下的所有 App 下是一样的,开发者可以用该唯一标识符与自己后台系统的账号体系绑定起来。
        NSString * userID = credential.user;

        // 苹果用户信息 如果授权过,可能无法再次获取该信息
        NSPersonNameComponents * fullName = credential.fullName;
        NSString * email = credential.email;

        // 服务器验证需要使用的参数
        NSString * authorizationCode = [[NSString alloc] initWithData:credential.authorizationCode encoding:NSUTF8StringEncoding];
        NSString * identityToken = [[NSString alloc] initWithData:credential.identityToken encoding:NSUTF8StringEncoding];

        // 用于判断当前登录的苹果账号是否是一个真实用户,取值有:unsupported、unknown、likelyReal
        ASUserDetectionStatus realUserStatus = credential.realUserStatus;

        NSLog(@"userID: %@", userID);
        NSLog(@"fullName: %@", fullName);
        NSLog(@"email: %@", email);
        NSLog(@"authorizationCode: %@", authorizationCode);
        NSLog(@"identityToken: %@", identityToken);
        NSLog(@"realUserStatus: %@", @(realUserStatus));
    }
    else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]) {

        // 用户登录使用现有的密码凭证
        ASPasswordCredential * passwordCredential = authorization.credential;
        // 密码凭证对象的用户标识 用户的唯一标识
        NSString * user = passwordCredential.user;
        // 密码凭证对象的密码
        NSString * password = passwordCredential.password;

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

    } else {

    }
}

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;
    }
    NSLog(@"%@", errorMsg);
}

用户注销 AppleId 或 停止使用 Apple ID 的状态处理

1、在 application:didFinishLaunchingWithOptions: 里处理

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

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

        // 注意 存储用户标识信息需要使用钥匙串来存储 这里使用NSUserDefaults 做的简单示例
        NSString * userIdentifier = [[NSUserDefaults standardUserDefaults] valueForKey:@"appleID"];

        if (userIdentifier) {

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

            [appleIDProvider getCredentialStateForUserID:userIdentifier
                                              completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError * _Nullable error) {
                switch (credentialState) {
                    case ASAuthorizationAppleIDProviderCredentialAuthorized:
                        // 授权状态有效
                        break;
                    case ASAuthorizationAppleIDProviderCredentialRevoked:
                        // 苹果账号登录的凭据已被移除,需解除绑定并重新引导用户使用苹果登录
                        break;
                    case ASAuthorizationAppleIDProviderCredentialNotFound:
                        // 未登录授权,直接弹出登录页面,引导用户登录
                        break;
                    case ASAuthorizationAppleIDProviderCredentialTransferred:
                        // 授权AppleID提供者凭据转移
                        break;
                }
            }];
        }
    }

    return YES;

}

2、监听 ASAuthorizationAppleIDProviderCredentialRevokedNotification 通知

// 注册通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleSignInWithAppleStateChanged:) name:ASAuthorizationAppleIDProviderCredentialRevokedNotification object:nil];

#pragma mark- apple授权状态 更改通知
- (void)handleSignInWithAppleStateChanged:(NSNotification *)notification
{
    NSLog(@"%@", notification.userInfo);
}

demo

https://github.com/quanweiwang/SignInWithApple/tree/master