- 原文博客地址: Sign In With Apple
- 在之前的文章iOS13適配深色模式(Dark Mode)中只是簡單提到了關(guān)于Sign In With Apple的問題, 下面就著重介紹一下什么是
Apple
登錄 - 對于很多應(yīng)用都會(huì)有自己的賬號(hào)登錄體系, 但是一般都相對繁瑣, 或者用戶會(huì)忘記密碼等, 為此一般都會(huì)接入微信、
QQ
登錄, 國外應(yīng)用也會(huì)有Google
匾乓、Facebook
等第三方登錄方式 - 在
WWDC 2019
上, 蘋果要求使用第三方登錄的應(yīng)用也必須接入蘋果賬號(hào)登錄(2020年必須適配) - 當(dāng)然了如果你的
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
為用戶提供一種快速安全的登錄方式, 用戶可以輕松登錄開發(fā)者的應(yīng)用和網(wǎng)站 - 使用
Apple
登錄可以讓用戶在系統(tǒng)中設(shè)置用戶帳戶,開發(fā)者可以獲取到用戶名稱(Name
), 用戶唯一標(biāo)識(shí)符(ID
)以及經(jīng)過驗(yàn)證的電子郵件地址(email
) -
Sign In with Apple
相關(guān)特性- 尊重用戶隱私: 開發(fā)人員僅僅只能獲取到用戶的姓名和郵箱, 蘋果也不會(huì)收集用戶和應(yīng)用交互的任何信息
- 系統(tǒng)內(nèi)置的安全性:
2F
雙重驗(yàn)證(Face ID
或Touch ID
)挖息,從此登錄不再需要密碼 - 簡化賬號(hào)的創(chuàng)建和登錄流程掉丽,無縫跨設(shè)備使用
- 開發(fā)者可以獲取到已驗(yàn)證過的郵箱作為登錄賬號(hào)或者與用戶進(jìn)行通信(注:用戶可以選擇隱藏真實(shí)郵箱,并使用蘋果提供的虛擬郵箱進(jìn)行授權(quán))
- 可跨平臺(tái)使用, Apple登錄支持
iOS
地熄,macOS
,tvOS
和watchOS
以及JavaScript
- 更多信息可慘考
使用Apple登錄
在代碼集成之前還需要做一些準(zhǔn)備工作
- 在開發(fā)者網(wǎng)站芯杀,在需要添加
Sign in with Apple
功能
- 在
Xcode
里面開啟Sign in with Apple
功能
登錄按鈕
Apple
蘋果登錄按鈕, 需要使用ASAuthorizationAppleIDButton
類創(chuàng)建添加, 該類是iOS 13
蘋果提供的創(chuàng)建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)
// 設(shè)置按鈕的圓切角
open var cornerRadius: CGFloat
}
開始創(chuàng)建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
的初始化方法中有兩個(gè)參數(shù)type
和style
-
type
是設(shè)置按鈕的類型ASAuthorizationAppleIDButton.ButtonType
-
style
設(shè)置按鈕的樣式ASAuthorizationAppleIDButton.Style
- 可參考官網(wǎng)介紹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
設(shè)置按鈕的圓角大小
// 默認(rèn)值大概5左右, 具體值不知
appleButton.cornerRadius = 10
發(fā)起授權(quán)請求
- 在創(chuàng)建好登錄按鈕后, 點(diǎn)擊按鈕的操作就是, 根據(jù)用戶登錄的
AppleID
發(fā)起授權(quán)請求, 并獲得授權(quán)碼 -
iOS 13
系統(tǒng)給我們提供了一個(gè)ASAuthorizationAppleIDProvider
類 - 該類就是一種基于用戶的
AppleID
生成用戶的授權(quán)請求的一種機(jī)制 - 在發(fā)起授權(quán)請求之前, 需要配置要獲取的數(shù)據(jù)權(quán)限范圍(例如:用戶名端考、郵箱等)
- 為獲取授權(quán)結(jié)果, 還需要設(shè)置回調(diào)代理, 并發(fā)起授權(quán)請求
@objc fileprivate func appleAction() {
// 基于用戶的Apple ID授權(quán)用戶,生成用戶授權(quán)請求的一種機(jī)制
let appleIDProvider = ASAuthorizationAppleIDProvider()
// 創(chuàng)建新的AppleID授權(quán)請求
let request = appleIDProvider.createRequest()
// 所需要請求的聯(lián)系信息
request.requestedScopes = [.fullName, .email]
// 管理授權(quán)請求的控制器
let controller = ASAuthorizationController(authorizationRequests: [request])
// 授權(quán)成功或者失敗的代理
controller.delegate = self
// 顯示上下文的代理, 系統(tǒng)可以在上下文中向用戶展示授權(quán)頁面
controller.presentationContextProvider = self
// 在控制器初始化期間啟動(dòng)授權(quán)流
controller.performRequests()
}
delegate
設(shè)置授權(quán)控制器通知授權(quán)請求的成功與失敗的代理
// 代理
weak open var delegate: ASAuthorizationControllerDelegate?
// 代理方法如下
@available(iOS 13.0, *)
public protocol ASAuthorizationControllerDelegate : NSObjectProtocol {
// 授權(quán)成功的回調(diào)
optional func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization)
// 授權(quán)失敗的回調(diào)
optional func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error)
}
presentationContextProvider
需要向用戶展示授權(quán)頁面時(shí), 需要遵循該協(xié)議
// 顯示上下文的代理, 系統(tǒng)可以在上下文中向用戶展示授權(quán)頁面
weak open var presentationContextProvider: ASAuthorizationControllerPresentationContextProviding?
// 協(xié)議方法
@available(iOS 13.0, *)
public protocol ASAuthorizationControllerPresentationContextProviding : NSObjectProtocol {
// 該方法返回一個(gè)視圖錨點(diǎn), 告訴代理應(yīng)該在哪個(gè)window 展示內(nèi)容給用戶
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor
}
// 方法執(zhí)行示例
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
return self.view.window!
}
ASAuthorization
- 在控制器獲得授權(quán)的成功回調(diào)中, 協(xié)議方法提供了一個(gè)
ASAuthorization
-
ASAuthorization
是對控制器成功授權(quán)的封裝, 包括兩個(gè)屬性
@available(iOS 13.0, *)
open class ASAuthorization : NSObject {
// 創(chuàng)建發(fā)起成功授權(quán)的發(fā)起者
open var provider: ASAuthorizationProvider { get }
// 成功授權(quán)后返回的相關(guān)憑證, 包含授權(quán)后的相關(guān)信息,是一個(gè)協(xié)議
open var credential: ASAuthorizationCredential { get }
}
ASAuthorizationCredential
是一個(gè)協(xié)議, 在處理授權(quán)成功的結(jié)果中, 需要使用遵循該協(xié)議的類, 有以下三個(gè)
-
ASPasswordCredential
: 密碼憑證 -
ASAuthorizationAppleIDCredential
: Apple ID身份驗(yàn)證成功產(chǎn)生的憑證 -
ASAuthorizationSingleSignOnCredential
: 單點(diǎn)登錄(SSO)身份驗(yàn)證產(chǎn)生的憑據(jù)
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關(guān)聯(lián)的用戶ID(標(biāo)識(shí)符)
open var user: String { get }
// 傳送給ASAuthorizationRequest的字符串
open var state: String? { get }
// 用戶授權(quán)的可訪問的聯(lián)系信息的種類
open var authorizedScopes: [ASAuthorization.Scope] { get }
// 為APP提供的授權(quán)證明的有效token
open var authorizationCode: Data? { get }
// JSON Web Token (JWT), 用于以安全的方式向應(yīng)用程序傳遞關(guān)于用戶身份的信息
open var identityToken: Data? { get }
// 用戶的email
open var email: String? { get }
// 用戶名
open var fullName: PersonNameComponents? { get }
// 用戶是否是真實(shí)用戶的狀態(tài)
open var realUserStatus: ASUserDetectionStatus { get }
}
// 用戶是否是真實(shí)用戶的枚舉值
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 }
// 用戶獲取授權(quán)范圍的token
open var accessToken: Data? { get }
// JSON Web Token (JWT), 用于以安全的方式向應(yīng)用程序傳遞關(guān)于用戶身份的信息
open var identityToken: Data? { get }
// 用戶授權(quán)的可訪問的聯(lián)系信息的種類
open var authorizedScopes: [ASAuthorization.Scope] { get }
// 完整的身份驗(yàn)證響應(yīng)信息
@NSCopying open var authenticatedResponse: HTTPURLResponse? { get }
}
授權(quán)成功
上面有提到, 在ASAuthorizationControllerDelegate
有兩個(gè)協(xié)議方法, 分別是授權(quán)成功和失敗的回調(diào), 下面就具體看看
extension SignViewController: ASAuthorizationControllerDelegate {
// 處理成功的授權(quán)
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
print("授權(quán)成功")
// 成功的Apple ID身份驗(yàn)證信息
if let appleIDCreden = authorization.credential as? ASAuthorizationAppleIDCredential {
let userIdentifier = appleIDCreden.user
let fullName = appleIDCreden.fullName
let email = appleIDCreden.email
// 這里需要我們在系統(tǒng)中創(chuàng)建一個(gè)賬戶, 用于存儲(chǔ)用戶的唯一標(biāo)識(shí)userIdentifier
// 可以在系統(tǒng)的鑰匙串中存儲(chǔ)
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 {
// 密碼憑證用戶的唯一標(biāo)識(shí)
let userIdentifiler = passwordCreden.user
// 密碼憑證的密碼
let password = passwordCreden.password
// 顯示相關(guān)信息
let message = "APP已經(jīng)收到您選擇的秘鑰憑證\nUsername: \(userIdentifiler)\n Password: \(password)"
showLabel.text = message
} else {
showLabel.text = "授權(quán)信息均不符"
}
}
// 處理授權(quán)錯(cuò)誤
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
print("授權(quán)錯(cuò)誤: \(error)")
var showText = ""
if let authError = error as? ASAuthorizationError {
let code = authError.code
switch code {
case .canceled:
showText = "用戶取消了授權(quán)請求"
case .failed:
showText = "授權(quán)請求失敗"
case .invalidResponse:
showText = "授權(quán)請求響應(yīng)無效"
case .notHandled:
showText = "未能處理授權(quán)請求"
case .unknown:
showText = "授權(quán)請求失敗, 未知的錯(cuò)誤原因"
default:
showText = "其他未知的錯(cuò)誤原因"
}
}
showLabel.text = showText
}
}
做好了上面配置, 就可以看到下面的登錄頁面
- 如果不修改姓名, 授權(quán)成功后將獲取到用戶的姓名
- 如果選擇共享我的電子郵件, 授權(quán)成功將獲取到用戶的電子郵件地址
- 如果選擇隱藏郵件地址, 授權(quán)成功將獲取到一個(gè)虛擬的電子郵件地址
- 點(diǎn)擊姓名右側(cè)的清除按鈕可以修改用戶名, 如下頁面
- 如果登錄用戶修改了用戶名, 那么授權(quán)成功后獲取到的用戶名就是修改后的
- 使用過
AppleID
登錄過App
揭厚,進(jìn)入應(yīng)用的時(shí)候會(huì)提示使用TouchID
登錄的場景如下
- 如果使用指紋登錄三次失敗后, 下面會(huì)有一個(gè)使用密碼繼續(xù)的按鈕, 可以使用手機(jī)密碼繼續(xù)登錄
- 如果手機(jī)沒有設(shè)置
Apple ID
, 使用蘋果登錄, 將會(huì)有彈窗提示,
監(jiān)聽授權(quán)狀態(tài)
- 在特殊情況下我們還需要監(jiān)聽授權(quán)狀態(tài)的改變, 并進(jìn)行相應(yīng)的處理
- 用戶終止在該
App
中使用Sign in with Apple
功能 - 用戶在設(shè)置里注銷了
Apple ID
- 針對類似這種情況,
App
需要獲取到這些狀態(tài)却特,然后做退出登錄操作 - 我們需要在
App
啟動(dòng)的時(shí)候,來獲取當(dāng)前用戶的授權(quán)狀態(tài)
// ASAuthorizationAppleIDProvider提供了一個(gè)獲取用戶授權(quán)狀態(tài)和授權(quán)憑據(jù)是否有效
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授權(quán)用戶棋弥,生成用戶授權(quán)請求的一種機(jī)制
let appleIDProvider = ASAuthorizationAppleIDProvider()
// 返回完成處理程序中給定用戶的憑據(jù)狀態(tài)
appleIDProvider.getCredentialState(forUserID: userIdentifier) { (state, error) in
switch state {
case .authorized:
print("授權(quán)狀態(tài)有效")
case .notFound:
print("授權(quán)憑證缺失(可能是使用AppleID 登錄過App)")
case .revoked:
print("上次使用蘋果賬號(hào)登錄的憑據(jù)已被移除核偿,需解除綁定并重新引導(dǎo)用戶使用蘋果登錄")
default:
print("未知狀態(tài)")
}
}
}
}
return true
}
除此之外還可以通過通知方法來監(jiān)聽revoked
狀態(tài), 在ASAuthorizationAppleIDProvider
中增加了一個(gè)屬性, 用于監(jiān)聽revoked
狀態(tài)
@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("授權(quán)狀態(tài)發(fā)生改變")
}
參考文檔
- 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后臺(tái)配置
歡迎您掃一掃下面的微信公眾號(hào),訂閱我的博客顽染!