- 原文博客地址: Sign In With Apple
- 在之前的文章iOS13適配深色模式(Dark Mode)中只是簡單提到了關于Sign In With Apple的問題, 下面就著重介紹一下什么是
Apple
登錄 - 對于很多應用都會有自己的賬號登錄體系, 但是一般都相對繁瑣, 或者用戶會忘記密碼等, 為此一般都會接入微信、
QQ
登錄, 國外應用也會有Google
、Facebook
等第三方登錄方式 - 在
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 ID
或Touch ID
),從此登錄不再需要密碼 - 簡化賬號的創建和登錄流程,無縫跨設備使用
- 開發者可以獲取到已驗證過的郵箱作為登錄賬號或者與用戶進行通信(注:用戶可以選擇隱藏真實郵箱,并使用蘋果提供的虛擬郵箱進行授權)
- 可跨平臺使用, Apple登錄支持
iOS
,macOS
,tvOS
和watchOS
以及JavaScript
- 更多信息可慘考
使用Apple登錄
在代碼集成之前還需要做一些準備工作
- 在開發者網站,在需要添加
Sign in with Apple
功能
- 在
Xcode
里面開啟Sign in with Apple
功能
登錄按鈕
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
的初始化方法中有兩個參數type
和style
-
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
展示效果如下
Style
@available(iOS 13.0, *)
public enum Style : Int {
case white
case whiteOutline
case black
}
不同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
}
}
做好了上面配置, 就可以看到下面的登錄頁面
- 如果不修改姓名, 授權成功后將獲取到用戶的姓名
- 如果選擇共享我的電子郵件, 授權成功將獲取到用戶的電子郵件地址
- 如果選擇隱藏郵件地址, 授權成功將獲取到一個虛擬的電子郵件地址
- 點擊姓名右側的清除按鈕可以修改用戶名, 如下頁面
- 如果登錄用戶修改了用戶名, 那么授權成功后獲取到的用戶名就是修改后的
- 使用過
AppleID
登錄過App
,進入應用的時候會提示使用TouchID
登錄的場景如下
- 如果使用指紋登錄三次失敗后, 下面會有一個使用密碼繼續的按鈕, 可以使用手機密碼繼續登錄
- 如果手機沒有設置
Apple ID
, 使用蘋果登錄, 將會有彈窗提示,
監聽授權狀態
- 在特殊情況下我們還需要監聽授權狀態的改變, 并進行相應的處理
- 用戶終止在該
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("授權狀態發生改變")
}
參考文檔
- Sign In with Apple Entitlement
- Generate and validate tokens
- Adding the Sign In with Apple Flow to Your App
- Sign In With Apple官方Demo(Swift版)
- Sign In with Apple后臺配置
歡迎您掃一掃下面的微信公眾號,訂閱我的博客!