Sign In with Apple

RunTitan發表於2019-10-08

image

  • 原文部落格地址: Sign In With Apple
  • 在之前的文章iOS13適配深色模式(Dark Mode)中只是簡單提到了關於Sign In With Apple的問題, 下面就著重介紹一下什麼是Apple登入
  • 對於很多應用都會有自己的賬號登入體系, 但是一般都相對繁瑣, 或者使用者會忘記密碼等, 為此一般都會接入微信、QQ登入, 國外應用也會有GoogleFacebook等第三方登入方式
  • WWDC 2019上, 蘋果要求使用第三方登入的應用也必須接入蘋果賬號登入(2020年必須適配)
  • 當然瞭如果你的App沒有提供第三方登入,那就不用整合; 如果用到了第三方登入,那麼需要提供Sign in with Apple

Sign in with Apple

Sign in with Apple makes it easy for users to sign in to your apps and websites using their Apple ID. Instead of filling out forms, verifying email addresses, and choosing new passwords, they can use Sign in with Apple to set up an account and start using your app right away. All accounts are protected with two-factor authentication for superior security, and Apple will not track users’ activity in your app or website.

Make signing in easy

  • Sign In with Apple為使用者提供一種快速安全的登入方式, 使用者可以輕鬆登入開發者的應用和網站
  • 使用Apple登入可以讓使用者在系統中設定使用者帳戶,開發者可以獲取到使用者名稱稱(Name), 使用者唯一識別符號(ID)以及經過驗證的電子郵件地址(email)
  • Sign In with Apple相關特性
    • 尊重使用者隱私: 開發人員僅僅只能獲取到使用者的姓名和郵箱, 蘋果也不會收集使用者和應用互動的任何資訊
    • 系統內建的安全性:2F雙重驗證(Face IDTouch ID),從此登入不再需要密碼
    • 簡化賬號的建立和登入流程,無縫跨裝置使用
    • 開發者可以獲取到已驗證過的郵箱作為登入賬號或者與使用者進行通訊(注:使用者可以選擇隱藏真實郵箱,並使用蘋果提供的虛擬郵箱進行授權)
    • 可跨平臺使用, Apple登入支援iOSmacOStvOSwatchOS以及JavaScript
    • 更多資訊可慘考 使用Apple登入

在程式碼整合之前還需要做一些準備工作

  1. 在開發者網站,在需要新增Sign in with Apple功能

image

  1. Xcode裡面開啟Sign in with Apple功能

Xcode

登入按鈕

Apple蘋果登入按鈕, 需要使用ASAuthorizationAppleIDButton類建立新增, 該類是iOS 13蘋果提供的建立Apple登入按鈕的專屬類

@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)

    // 設定按鈕的圓切角
    open var cornerRadius: CGFloat
}
複製程式碼

開始建立Apple登入按鈕

// apple登入按鈕
let appleButton = ASAuthorizationAppleIDButton(type: .continue, style: .black)
appleButton.frame = CGRect(x: 100, y: showLabel.frame.maxY + 40, width: 200, height: 50)
appleButton.cornerRadius = 10
appleButton.addTarget(self, action: #selector(appleAction), for: .touchUpInside)
view.addSubview(appleButton)
複製程式碼
  • ASAuthorizationAppleIDButton的初始化方法中有兩個引數typestyle
  • type是設定按鈕的型別ASAuthorizationAppleIDButton.ButtonType
  • style設定按鈕的樣式ASAuthorizationAppleIDButton.Style
  • 可參考官網介紹Sign In with Apple

ButtonType

public enum ButtonType : Int {
    // signIn登入型別
    case signIn
    // continue型別
    case `continue`

    public static var `default`: ASAuthorizationAppleIDButton.ButtonType { get }
}
複製程式碼

不同ButtonType展示效果如下

ButtonType

Style

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

    case whiteOutline

    case black
}
複製程式碼

不同Style展示效果和使用場景如下

Style

cornerRadius

設定按鈕的圓角大小

// 預設值大概5左右, 具體值不知
appleButton.cornerRadius = 10
複製程式碼

發起授權請求

  • 在建立好登入按鈕後, 點選按鈕的操作就是, 根據使用者登入的AppleID發起授權請求, 並獲得授權碼
  • iOS 13系統給我們提供了一個ASAuthorizationAppleIDProvider
  • 該類就是一種基於使用者的AppleID生成使用者的授權請求的一種機制
  • 在發起授權請求之前, 需要配置要獲取的資料許可權範圍(例如:使用者名稱、郵箱等)
  • 為獲取授權結果, 還需要設定回撥代理, 併發起授權請求
@objc fileprivate func appleAction() {
    // 基於使用者的Apple ID授權使用者,生成使用者授權請求的一種機制
    let appleIDProvider = ASAuthorizationAppleIDProvider()
    // 建立新的AppleID授權請求
    let request = appleIDProvider.createRequest()
    // 所需要請求的聯絡資訊
    request.requestedScopes = [.fullName, .email]
    // 管理授權請求的控制器
    let controller = ASAuthorizationController(authorizationRequests: [request])
    // 授權成功或者失敗的代理
    controller.delegate = self
    // 顯示上下文的代理, 系統可以在上下文中向使用者展示授權頁面
    controller.presentationContextProvider = self
    // 在控制器初始化期間啟動授權流
    controller.performRequests()
}
複製程式碼

delegate

設定授權控制器通知授權請求的成功與失敗的代理

// 代理
weak open var delegate: ASAuthorizationControllerDelegate?

// 代理方法如下
@available(iOS 13.0, *)
public protocol ASAuthorizationControllerDelegate : NSObjectProtocol {
    // 授權成功的回撥
    optional func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization)

    // 授權失敗的回撥
    optional func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error)
}
複製程式碼

presentationContextProvider

需要向使用者展示授權頁面時, 需要遵循該協議

// 顯示上下文的代理, 系統可以在上下文中向使用者展示授權頁面
weak open var presentationContextProvider: ASAuthorizationControllerPresentationContextProviding?

// 協議方法
@available(iOS 13.0, *)
public protocol ASAuthorizationControllerPresentationContextProviding : NSObjectProtocol {

    // 該方法返回一個檢視錨點, 告訴代理應該在哪個window 展示內容給使用者
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor
}


// 方法執行示例
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
    return self.view.window!
}
複製程式碼

ASAuthorization

  • 在控制器獲得授權的成功回撥中, 協議方法提供了一個ASAuthorization
  • ASAuthorization是對控制器成功授權的封裝, 包括兩個屬性
@available(iOS 13.0, *)
open class ASAuthorization : NSObject {

    
    // 建立發起成功授權的發起者
    open var provider: ASAuthorizationProvider { get }

    // 成功授權後返回的相關憑證, 包含授權後的相關資訊,是一個協議
    open var credential: ASAuthorizationCredential { get }
}
複製程式碼

ASAuthorizationCredential

是一個協議, 在處理授權成功的結果中, 需要使用遵循該協議的類, 有以下三個

  • ASPasswordCredential: 密碼憑證
  • ASAuthorizationAppleIDCredential: Apple ID身份驗證成功產生的憑證
  • ASAuthorizationSingleSignOnCredential: 單點登入(SSO)身份驗證產生的憑據

ASPasswordCredential

@available(iOS 12.0, *)
open class ASPasswordCredential : NSObject, ASAuthorizationCredential {

    // 初始化方法
    public init(user: String, password: String)

    // 使用者名稱
    open var user: String { get }

    // 使用者密碼
    open var password: String { get }
}
複製程式碼

ASAuthorizationAppleIDCredential

@available(iOS 13.0, *)
open class ASAuthorizationAppleIDCredential : NSObject, ASAuthorizationCredential {

    // 和使用者AppleID關聯的使用者ID(識別符號)
    open var user: String { get }

    // 傳送給ASAuthorizationRequest的字串
    open var state: String? { get }

    // 使用者授權的可訪問的聯絡資訊的種類
    open var authorizedScopes: [ASAuthorization.Scope] { get }

    // 為APP提供的授權證明的有效token
    open var authorizationCode: Data? { get }

    // JSON Web Token (JWT), 用於以安全的方式嚮應用程式傳遞關於使用者身份的資訊
    open var identityToken: Data? { get }

    // 使用者的email
    open var email: String? { get }

    // 使用者名稱
    open var fullName: PersonNameComponents? { get }

    // 使用者是否是真實使用者的狀態
    open var realUserStatus: ASUserDetectionStatus { get }
}

// 使用者是否是真實使用者的列舉值
public enum ASUserDetectionStatus : Int {

    case unsupported

    case unknown

    case likelyReal
}
複製程式碼

ASAuthorizationSingleSignOnCredential

@available(iOS 13.0, *)
open class ASAuthorizationSingleSignOnCredential : NSObject, ASAuthorizationCredential {

    // AuthenticationServices返回的字串
    open var state: String? { get }

    // 使用者獲取授權範圍的token
    open var accessToken: Data? { get }

    // JSON Web Token (JWT), 用於以安全的方式嚮應用程式傳遞關於使用者身份的資訊
    open var identityToken: Data? { get }

    // 使用者授權的可訪問的聯絡資訊的種類
    open var authorizedScopes: [ASAuthorization.Scope] { get }

    // 完整的身份驗證響應資訊
    @NSCopying open var authenticatedResponse: HTTPURLResponse? { get }
}
複製程式碼

授權成功

上面有提到, 在ASAuthorizationControllerDelegate有兩個協議方法, 分別是授權成功和失敗的回撥, 下面就具體看看

extension SignViewController: ASAuthorizationControllerDelegate {
    // 處理成功的授權
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        print("授權成功")
        // 成功的Apple ID身份驗證資訊
        if let appleIDCreden = authorization.credential as? ASAuthorizationAppleIDCredential {
            let userIdentifier = appleIDCreden.user
            let fullName = appleIDCreden.fullName
            let email = appleIDCreden.email
            
            // 這裡需要我們在系統中建立一個賬戶, 用於儲存使用者的唯一標識userIdentifier
            // 可以在系統的鑰匙串中儲存
            let webVC = WebViewController()
            webVC.user = userIdentifier
            webVC.giveName = fullName?.givenName ?? ""
            webVC.familyName = fullName?.familyName ?? ""
            webVC.email = email ?? ""
            navigationController?.pushViewController(webVC, animated: true)
        } else if let passwordCreden = authorization.credential as? ASPasswordCredential {
            // 密碼憑證使用者的唯一標識
            let userIdentifiler = passwordCreden.user
            // 密碼憑證的密碼
            let password = passwordCreden.password
            
            // 顯示相關資訊
            let message = "APP已經收到您選擇的祕鑰憑證\nUsername: \(userIdentifiler)\n Password: \(password)"
            showLabel.text = message
        } else {
            showLabel.text = "授權資訊均不符"
        }
    }
    
    // 處理授權錯誤
    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        print("授權錯誤: \(error)")
        
        var showText = ""
        if let authError = error as? ASAuthorizationError {
            let code = authError.code
            switch code {
            case .canceled:
                showText = "使用者取消了授權請求"
            case .failed:
                showText = "授權請求失敗"
            case .invalidResponse:
                showText = "授權請求響應無效"
            case .notHandled:
                showText = "未能處理授權請求"
            case .unknown:
                showText = "授權請求失敗, 未知的錯誤原因"
            default:
                showText = "其他未知的錯誤原因"
            }
        }
        showLabel.text = showText
    }
}
複製程式碼

做好了上面配置, 就可以看到下面的登入頁面

image

  • 如果不修改姓名, 授權成功後將獲取到使用者的姓名
  • 如果選擇共享我的電子郵件, 授權成功將獲取到使用者的電子郵件地址
  • 如果選擇隱藏郵件地址, 授權成功將獲取到一個虛擬的電子郵件地址
  • 點選姓名右側的清除按鈕可以修改使用者名稱, 如下頁面

name

  • 如果登入使用者修改了使用者名稱, 那麼授權成功後獲取到的使用者名稱就是修改後的
  • 使用過AppleID登入過App,進入應用的時候會提示使用TouchID登入的場景如下

image

  • 如果使用指紋登入三次失敗後, 下面會有一個使用密碼繼續的按鈕, 可以使用手機密碼繼續登入
  • 如果手機沒有設定Apple ID, 使用蘋果登入, 將會有彈窗提示,

image

監聽授權狀態

  • 在特殊情況下我們還需要監聽授權狀態的改變, 並進行相應的處理
  • 使用者終止在該App中使用Sign in with Apple功能
  • 使用者在設定裡登出了Apple ID
  • 針對類似這種情況, App需要獲取到這些狀態,然後做退出登入操作
  • 我們需要在App啟動的時候,來獲取當前使用者的授權狀態
// ASAuthorizationAppleIDProvider提供了一個獲取使用者授權狀態和授權憑據是否有效
func getCredentialState(forUserID: String, completion: (ASAuthorizationAppleIDProvider.CredentialState, Error?) -> Void)


// ASAuthorizationAppleIDProvider.CredentialState的所有列舉值
public enum CredentialState : Int {
    case revoked
    case authorized
    case notFound
    case transferred
}
複製程式碼

示例程式碼如下

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    if #available(iOS 13.0, *) {
        // 鑰匙串中取出的
        let userIdentifier = "userIdentifier"
        if (!userIdentifier.isEmpty) {
            // 基於使用者的Apple ID授權使用者,生成使用者授權請求的一種機制
            let appleIDProvider = ASAuthorizationAppleIDProvider()
            // 返回完成處理程式中給定使用者的憑據狀態
            appleIDProvider.getCredentialState(forUserID: userIdentifier) { (state, error) in
                switch state {
                case .authorized:
                    print("授權狀態有效")
                case .notFound:
                    print("授權憑證缺失(可能是使用AppleID 登入過App)")
                case .revoked:
                    print("上次使用蘋果賬號登入的憑據已被移除,需解除繫結並重新引導使用者使用蘋果登入")
                default:
                    print("未知狀態")
                }
            }
        }
    }
    
    return true
}
複製程式碼

除此之外還可以通過通知方法來監聽revoked狀態, 在ASAuthorizationAppleIDProvider中增加了一個屬性, 用於監聽revoked狀態

@available(iOS 13.0, *)
public class let credentialRevokedNotification: NSNotification.Name

// 使用方法
fileprivate func observeAppleSignInState() {
    if #available(iOS 13.0, *) {
        let center = NotificationCenter.default
        center.addObserver(self, selector: #selector(handleStateChange(noti:)), name: ASAuthorizationAppleIDProvider.credentialRevokedNotification, object: nil)
    }
}

@objc fileprivate func handleStateChange(noti: Any) {
    print("授權狀態發生改變")
}
複製程式碼

參考文件


歡迎您掃一掃下面的微信公眾號,訂閱我的部落格!

微信公眾號

相關文章