苹果登录 (apple sign)

背景

在今年(2019)的开发者大会上(北京时间6月4号),苹果宣布支持应用可以通过苹果登录这一第三方登录自身app.

当然,审核条款也更新了.

4.8 通过 Apple 登录
如果 app 专门使用第三方或社交登录服务 (例如,Facebook 登录、Google 登录、通过 Twitter 登录、通过 LinkedIn 登录、通过 Amazon 登录或微信登录) 来对其进行设置或验证这个 app 的用户主帐户,则该 app 必须同时提供“通过 Apple 登录”作为等效选项。用户的主帐户是指在 app 中建立的、用于标识身份、登录和访问功能和相关服务的帐户。

在以下情况下,不要求提供“通过 Apple 登录”选项:

  • 您的 app 仅使用公司自有的帐户设置和登录系统。
  • 您的 app 是一款教育、企业或商务 app,要求用户使用现有的教育或企业帐户登录。
  • 您的 app 使用政府或行业支持的公民身份系统或电子身份证来鉴定用户身份。
  • 您的 app 是特定第三方服务的客户端,用户需要使用他们的邮件、社交媒体或其他第三方帐户直接登录才能访问内容。

所以,如果你的app符合上面要求,那么苹果登录就少不了,或者你觉得给用户多一种第三方登录选择,也可以选择接进来.

这个没有一点难度,和接入qq,微信一样的.超级简单.

需使用xcode11,准备开发者账号并且证书勾选苹果登录权限

苹果原话(需要准备的东西)
Configure the Sample Code Project
Perform the following steps before building and running the app:

Set your development team in the Signing & Capabilities tab so Xcode can create a provisioning profile that uses the Sign In with Apple capability.

Choose a target device that you’re signed into with an Apple ID that uses Two-Factor Authentication.

大家跟着我走就是了.

也可以自己去苹果官方文档里.

友情提示下,苹果的官方代码是swift的.swift不会的要学习起来了.

首先,需要有个苹果登录按钮 .这是默认样式

Simulator Screen Shot - iPhone 11 Pro Max - 2019-10-10 at 21.58.13.png

它就长这样.苹果不建议我们改东西.只能用苹果提供的.就圆角和大小可以更换.有其他的要求的也可以试试.看看苹果会不会给过.文本可以添加多语言本地化.

这个类 ASAuthorizationAppleIDButton,继承UIControl


extension ASAuthorizationAppleIDButton {

    
    @available(iOS 13.0, *)
    public enum ButtonType : Int {

        
        case signIn

        case `continue`

        
        public static var `default`: ASAuthorizationAppleIDButton.ButtonType { get }
    }

    
    @available(iOS 13.0, *)
    public enum Style : Int {

        
        case white

        case whiteOutline

        case black
    }
}

@available(iOS 13.0, *)
open class ASAuthorizationAppleIDButton : UIControl {

    
    public convenience init(type: ASAuthorizationAppleIDButton.ButtonType, style: ASAuthorizationAppleIDButton.Style)

    
    public init(authorizationButtonType type: ASAuthorizationAppleIDButton.ButtonType, authorizationButtonStyle style: ASAuthorizationAppleIDButton.Style)

    
    /** @abstract Set a custom corner radius to be used by this button.
     */
    open var cornerRadius: CGFloat
}

//可改的东西好少

 let authorizationButton = ASAuthorizationAppleIDButton.init(authorizationButtonType: ASAuthorizationAppleIDButton.ButtonType.signIn, authorizationButtonStyle: ASAuthorizationAppleIDButton.Style.white)
Simulator Screen Shot - iPhone 11 Pro Max - 2019-10-10 at 22.08.18.png
let authorizationButton = ASAuthorizationAppleIDButton.init(authorizationButtonType: ASAuthorizationAppleIDButton.ButtonType.continue, authorizationButtonStyle: ASAuthorizationAppleIDButton.Style.white)
Simulator Screen Shot - iPhone 11 Pro Max - 2019-10-10 at 22.08.18.png
let authorizationButton = ASAuthorizationAppleIDButton.init(authorizationButtonType: ASAuthorizationAppleIDButton.ButtonType.continue, authorizationButtonStyle: ASAuthorizationAppleIDButton.Style.whiteOutline)
Simulator Screen Shot - iPhone 11 Pro Max - 2019-10-10 at 22.11.43.png

有按钮了,那就要开始点击了.

        let appleIDProvider = ASAuthorizationAppleIDProvider()
        let request = appleIDProvider.createRequest()
        request.requestedScopes = [.fullName, .email]
        
        let authorizationController =     
        ASAuthorizationController(authorizationRequests: [request])
        authorizationController.delegate = self
        authorizationController.presentationContextProvider = self
        authorizationController.performRequests()
extension LoginViewController: ASAuthorizationControllerPresentationContextProviding {
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
//告诉苹果弹窗到哪个window上
        return self.view.window!
    }
}

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

授权一次后,再次点击登录按钮,则会直接弹出正在登录.

//这是这次的,下次的呢. 直接可以通过keychain直接登录.
这个performExistingAccountSetupFlows放到你要登录的地方去.

/// Prompts the user if an existing iCloud Keychain credential or Apple ID credential is found.
    func performExistingAccountSetupFlows() {
        // Prepare requests for both Apple ID and password providers.
        let requests = [ASAuthorizationAppleIDProvider().createRequest(),
                        ASAuthorizationPasswordProvider().createRequest()]
        
        // Create an authorization controller with the given requests.
        let authorizationController = ASAuthorizationController(authorizationRequests: requests)
        authorizationController.delegate = self
        authorizationController.presentationContextProvider = self
        authorizationController.performRequests()
    }

下面是回调了

extension LoginViewController: ASAuthorizationControllerDelegate {
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
            
            let userIdentifier = appleIDCredential.user
            let fullName = appleIDCredential.fullName
            let email = appleIDCredential.email
            
            // Create an account in your system.
            // For the purpose of this demo app, store the userIdentifier in the keychain.
            do {
                try KeychainItem(service: "com.example.liknddd", account: "userIdentifier").saveItem(userIdentifier)
            } catch {
                print("Unable to save userIdentifier to keychain.")
            }
            
            // For the purpose of this demo app, show the Apple ID credential information in the ResultViewController.
            if let viewController = self.presentingViewController as? ResultViewController {
                DispatchQueue.main.async {
                    viewController.userIdentifierLabel.text = userIdentifier
                    if let givenName = fullName?.givenName {
                        viewController.givenNameLabel.text = givenName
                    }
                    if let familyName = fullName?.familyName {
                        viewController.familyNameLabel.text = familyName
                    }
                    if let email = email {
                        viewController.emailLabel.text = email
                    }
                    self.dismiss(animated: true, completion: nil)
                }
            }
        } else if let passwordCredential = authorization.credential as? ASPasswordCredential {
            // Sign in using an existing iCloud Keychain credential.
            let username = passwordCredential.user
            let password = passwordCredential.password
            
            // For the purpose of this demo app, show the password credential as an alert.
            DispatchQueue.main.async {
                let message = "The app has received your selected credential from the keychain. \n\n Username: \(username)\n Password: \(password)"
                let alertController = UIAlertController(title: "Keychain Credential Received",
                                                        message: message,
                                                        preferredStyle: .alert)
                alertController.addAction(UIAlertAction(title: "Dismiss", style: .cancel, handler: nil))
                self.present(alertController, animated: true, completion: nil)
            }
        }
    }
    
    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        // Handle 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;
    
    }
    }
}

能拿到userID、email、fullName、authorizationCode、identityToken 以及 realUserStatus 等信息。
∙ userID苹果用户唯一标识符,该值在同一个开发者账号下的所有 App下是一样的,开发者可以用该唯一标识符与自己后台系统的账号体系绑定起来。

∙ Verification data: Identity token, authorizationCode,验证数据,用于传给开发者后台服务器,然后开发者服务器再向苹果的身份验证服务端验证本次授权登录请求数据的有效性和真实性,详见 Sign In with Apple REST API。如果验证成功,可以根据 userIdentifier 判断账号是否已存在,若存在,则返回自己账号系统的登录态,若不存在,则创建一个新的账号,并返回对应的登录态给 App。

∙ Account information: Name, verified email,苹果用户信息,包括全名、邮箱等。

∙ Real user indicator: High confidence indicator that likely real user,用于判断当前登录的苹果账号是否是一个真实用户,取值有:unsupported、unknown、likelyReal。

意外情况处理

通过上面的步骤一个完整的授权,已经完成。BUT,我们还需要处理一些 Case。

  • 用户终止 App 中使用 Sign in with Apple 功能

  • 用户在设置里注销了 AppleId

这些情况下,App 需要获取到这些状态,然后做退出登录操作,或者重新登录。
我们需要在 App 启动的时候,通过 getCredentialState:completion: 来获取当前用户的授权状态。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    if (@available(iOS 13.0, *)) {
        NSString *userIdentifier = 钥匙串中取出的 userIdentifier;
        if (userIdentifier) {
            ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];
            [appleIDProvider getCredentialStateForUserID:userIdentifier
                                              completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState,
                                                           NSError * _Nullable error)
            {
                switch (credentialState) {
                    case ASAuthorizationAppleIDProviderCredentialAuthorized:
                        // The Apple ID credential is valid
                        break;
                    case ASAuthorizationAppleIDProviderCredentialRevoked:
                        // Apple ID Credential revoked, handle unlink
                        break;
                    case ASAuthorizationAppleIDProviderCredentialNotFound:
                        // Credential not found, show login UI
                        break;
                }
            }];
        }
    }
    
    return YES;
}

ASAuthorizationAppleIDProviderCredentialState 解析如下:

∙ASAuthorizationAppleIDProviderCredentialAuthorized 授权状态有效;∙ASAuthorizationAppleIDProviderCredentialRevoked 上次使用苹果账号登录的凭据已被移除,需解除绑定并重新引导用户使用苹果登录;

∙ASAuthorizationAppleIDProviderCredentialNotFound 未登录授权,直接弹出登录页面,引导用户登录。

另外,在 App 使用过程中,你还可以通过通知方法来监听 revoked 状态,可以添加 ASAuthorizationAppleIDProviderCredentialRevokedNotification 这个通知,收到这个通知的时候,我们可以:

∙ Sign user out on this device

∙ Guide to sign in again

具体怎么添加和处理,可以根据业务需求来决定。

- (void)observeAppleSignInState
{
    if (@available(iOS 13.0, *)) {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(handleSignInWithAppleStateChanged:)
                                                     name:ASAuthorizationAppleIDProviderCredentialRevokedNotification
                                                   object:nil];
    }
}
 
- (void)handleSignInWithAppleStateChanged:(NSNotification *)notification
{
    // Sign the user out, optionally guide them to sign in again
    NSLog(@"%@", notification.userInfo);
}

这是结尾,没了,各位!!!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容