- 原文部落格地址: 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後臺配置
歡迎您掃一掃下面的微信公眾號,訂閱我的部落格!