Objective-C 集成通过 Apple 登录

2020-06-23  本文已影响0人  独木舟的木

1. 项目中开启 Sign In With Apple 功能

在 Xcode 项目的 TARGETS 配置中,选择 Signing & Capabilities 选项卡。点击左上角的 “+” 按钮,搜索并添加 "Sign In with Apple" 功能。

2. 添加 AuthenticationServices 框架

在 TARGETS —> General —> Frameworks 下添加 AuthenticationServices 框架:

然后在所需的视图控制器中导入 <AuthenticationServices/AuthenticationServices.h> 框架:

#import<AuthenticationServices/AuthenticationServices.h>

3. 添加标识符

在这里,我创建了 setCurrentIdentifier 标识符对象用于保存当前用户,然后让当前视图控制器遵守 ASAuthorizationControllerDelegateASAuthorizationControllerPresentationContextProviding 代理。

#import "ViewController.h"
#import <AuthenticationServices/AuthenticationServices.h>

static NSString * const setCurrentIdentifier = @"setCurrentIdentifier";

@interface ViewController () <ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding>

@end

4. 创建并加载「通过 Apple 登录」按钮,发起授权

- (void)setupUI {
    // MARK: 添加 「通过 Apple 登录」按钮
    if (@available(iOS 13.0, *)) {
        ASAuthorizationAppleIDButton *appleIDButton = [ASAuthorizationAppleIDButton buttonWithType:ASAuthorizationAppleIDButtonTypeSignIn style:ASAuthorizationAppleIDButtonStyleWhite];
        // 注:根据 Apple 要求,「通过 Apple 登录」按钮的尺寸不得小于 140*30
        appleIDButton.frame = CGRectMake(50, 150, 200, 44);
        // 该按钮默认有圆角,当然,你也可以自定义圆角尺寸
        // appleIDButton.cornerRadius = 5;
        [appleIDButton addTarget:self action:@selector(handleAuthrization:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:appleIDButton];
    }
}

在按钮点击事件中,发起授权,获取用户的 Appid 身份信息,该方法需要实现的主要有两个部分:

  1. 使用 Provider 创建 Request
  2. 使用 ASAuthorizationController 实例执行 Request
- (void)handleAuthrization:(ASAuthorizationAppleIDButton *)button {
    if (@available(iOS 13.0, *)) {
        // 基于用户的 Apple ID 授权用户,生成用户授权请求的一种机制
        ASAuthorizationAppleIDProvider *appleIdProvider = [[ASAuthorizationAppleIDProvider alloc] init];
        
        // 创建新的 AppleID 授权请求
        ASAuthorizationAppleIDRequest *request = appleIdProvider.createRequest;
        // 在用户授权期间请求的联系信息
        request.requestedScopes = @[ASAuthorizationScopeEmail, ASAuthorizationScopeFullName];
        
        // 由 ASAuthorizationAppleIDProvider 创建的授权请求,管理授权请求的控制器
        ASAuthorizationController *controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
        
        // 设置授权控制器通知授权请求的成功与失败的代理
        controller.delegate = self;
        
        // 设置提供展示上下文的代理,在这个上下文中,系统可以向用户展示授权界面
        controller.presentationContextProvider = self;
        
         // 在控制器初始化期间启动授权流
        [controller performRequests];
    }
}

5. 实现授权回调协议(ASAuthorizationControllerDelegate)中的方法

授权成功的回调:

- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization NS_SWIFT_NAME(authorizationController(controller:didCompleteWithAuthorization:)) {
        
    if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
        // 用户登录使用 ASAuthorizationAppleIDCredential
        ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential;

        NSString *user = appleIDCredential.user;
        NSString *nickname = appleIDCredential.fullName.nickname;
        NSData *identityToken = appleIDCredential.identityToken;
        NSLog(@"user:%@, nickname:%@, identityToken:%@", user, nickname, identityToken);
        
        // 授权成功后,你可以拿到苹果返回的全部数据,根据需要和后台交互。
        // 需要使用钥匙串的方式保存用户的唯一信息,这里暂且处于测试阶段,NSUserDefaults
        // 保存apple返回的唯一标识符
        [[NSUserDefaults standardUserDefaults] setObject:user forKey:setCurrentIdentifier];
        [[NSUserDefaults standardUserDefaults] synchronize];
        
    } else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]) {
        // 用户登录使用现有的密码凭证
        ASPasswordCredential *psdCredential = authorization.credential;
        // 密码凭证对象的用户标识 用户的唯一标识
        NSString *user = psdCredential.user;
        // 密码凭证对象的密码
        NSString *psd = psdCredential.password;
        NSLog(@"psduser -  %@   %@",psd,user);
    } else {
        NSLog(@"授权信息不符");
    }
}

授权失败的回调:

- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error  NS_SWIFT_NAME(authorizationController(controller:didCompleteWithError:)) {
    
    NSString *errorMsg;
    switch (error.code) {
        case ASAuthorizationErrorUnknown: {
            errorMsg = @"授权请求失败,未知原因";
            break;
        }
        case ASAuthorizationErrorCanceled: {
            errorMsg = @"用户取消了授权请求";
            break;
        }
        case ASAuthorizationErrorInvalidResponse: {
            errorMsg = @"授权请求响应无效";
            break;
        }
        case ASAuthorizationErrorNotHandled: {
            errorMsg = @"未能处理授权请求";
            break;
        }
        case ASAuthorizationErrorFailed: {
            errorMsg = @"获取授权失败";
            break;
        }
    }
    
    NSLog(@"error message:%@", errorMsg);
}

以上代码中,授权失败状态(ASAuthorizationError)是一个枚举类型,我们通过 switch-case 语法遍历授权失败状态枚举值,并执行不同的处理流程。

typedef NS_ERROR_ENUM(ASAuthorizationErrorDomain, ASAuthorizationError) {
    ASAuthorizationErrorUnknown = 1000,         // 授权请求失败未知原因
    ASAuthorizationErrorCanceled = 1001,        // 用户取消了授权请求
    ASAuthorizationErrorInvalidResponse = 1002, // 授权请求响应无效
    ASAuthorizationErrorNotHandled = 1003,      // 未能处理授权请求
    ASAuthorizationErrorFailed = 1004,          // 授权请求失败
} API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0));

6. 实现显示授权控制器的回调方法(ASAuthorizationControllerPresentationContextProviding)

#pragma mark - <ASAuthorizationControllerPresentationContextProviding>

// 告诉代理应该在哪个 window 展示内容给用户
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller {
    return self.view.window;
}

6. 监听授权状态改变的通知

- (void)dealloc {
    if (@available(iOS 13.0, *)) {
        [[NSNotificationCenter defaultCenter] removeObserver:self name:ASAuthorizationAppleIDProviderCredentialRevokedNotification object:nil];
    }
}

- (void)observeAppleSignInState {
    // MARK: 监听通过 Apple 登录的授权状态,判断授权是否失效
    if (@available(iOS 13.0, *)) {
        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
        [center addObserver:self selector:@selector(handleSignInWithAppleStateChanged:) name:ASAuthorizationAppleIDProviderCredentialRevokedNotification object:nil];
    }
}

当授权状态改变时,执行的方法:

- (void)handleSignInWithAppleStateChanged:(NSNotification *)notification {
    NSLog(@"%s", __FUNCTION__);
    NSLog(@"%@", notification);
    
    if (@available(iOS 13.0,*)) {
        // 基于用户的 Apple ID 生成授权用户请求的机制
        ASAuthorizationAppleIDProvider *provider = [[ASAuthorizationAppleIDProvider alloc] init];
        
        // 注意 存储用户标识信息需要使用钥匙串来存储 这里笔者简单期间 使用NSUserDefaults 做的简单示例
        NSString *user = [[NSUserDefaults standardUserDefaults] objectForKey:@"userIdentifier"];
        NSLog(@"user11 -     %@",user);
        
        __block NSString *errorMsg;
        [provider getCredentialStateForUserID:user completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError * _Nullable error) {
            if (!error) {
                switch (credentialState) {
                    case ASAuthorizationAppleIDProviderCredentialRevoked: {
                        NSLog(@"Revoked");
                        errorMsg = @"苹果授权凭证失效";
                        break;
                    }
                    case ASAuthorizationAppleIDProviderCredentialAuthorized: {
                        NSLog(@"Authorized");
                        errorMsg = @"苹果授权凭证状态良好";
                        break;
                    }
                    case ASAuthorizationAppleIDProviderCredentialNotFound: {
                        NSLog(@"NotFound");
                        errorMsg = @"未发现苹果授权凭证";
                        break;
                    }
                    case ASAuthorizationAppleIDProviderCredentialTransferred: {
                        NSLog(@"CredentialTransferred");
                        errorMsg = @"未发现苹果授权凭证";
                        break;
                    }
                }
            } else {
                NSLog(@"state is failure");
            }
        }];
    }
}

其中,授权状态 ASAuthorizationAppleIDProviderCredentialState 是一个枚举类型:

typedef NS_ENUM(NSInteger, ASAuthorizationAppleIDProviderCredentialState) {
    // 授权状态失效(用户停止使用 AppID 登录 App)
    ASAuthorizationAppleIDProviderCredentialRevoked,
    // 已授权(用户已使用 AppleID 登录过 App)
    ASAuthorizationAppleIDProviderCredentialAuthorized,
    // 授权凭证缺失(可能是使用 AppleID 登录过 App)
    ASAuthorizationAppleIDProviderCredentialNotFound,
    // 授权 AppleID 提供者凭证已转移
    ASAuthorizationAppleIDProviderCredentialTransferred,
}

在以上方法中,需要通过 switch-case 语法遍历该枚举类型的不同状态,并执行不同的处理流程。

Apple ID 的唯一性

Apple 通过授权凭证(ASAuthorizationAppleIDCredential)的 user 属性返回用户 ID,即使用户取消授权,然后再重新授权,该 ID 属性的值并不会改变,仍然是唯一的。这里简单测试如下。
第一次发起授权,获取并返回 user 值如下:

000701.c78c577e89ac4c56af2208c05947ca41.0652

接着,打开系统设置-用户-密码与安全性-使用您 Apple ID 的 App - 在授权应用列表中删除该应用以取消授权。
重新打开应用,点击「通过 Apple 登录」按钮重新登录,系统返回的 user 值仍为:

000701.c78c577e89ac4c56af2208c05947ca41.0652

可见,对于同一个应用下的同一个 Apple ID 用户,返回的 user 值是唯一的。

源码参考:GitHub: Sign-In-with-Apple

Apple 文档

社区博客

上一篇下一篇

猜你喜欢

热点阅读