前言
時(shí)下,是一個(gè)信息爆炸的時(shí)代,信息的傳播途徑也是層出不窮,市面上出現(xiàn)了各種各樣的設(shè)備來收集、傳播、存儲(chǔ)我們的數(shù)據(jù),用戶的隱私受到了極大的挑戰(zhàn)。這些設(shè)備中智能手機(jī)尤為更勝,做為 iOS 開發(fā)者保護(hù)用于的數(shù)據(jù)也是我們應(yīng)盡的責(zé)任。今天我們討論的是用戶登錄數(shù)據(jù)如何免于泄露。
對(duì)于用戶登錄數(shù)據(jù)的保護(hù),大家首先想到的應(yīng)該就是 Keychain, 為了更進(jìn)一步確保數(shù)據(jù)安全蘋果公司還為我們提供了另外一層安全保護(hù)措施——Face ID 和 Touch ID.
iPhone 5s 以及更新的 iPhone 搭載了A7及更新的芯片,這些芯片允許您將用戶的生物特征數(shù)據(jù)存儲(chǔ)到一個(gè)安全的區(qū)域。也就是說,我們可以通過Face ID 或 Touch ID 將用戶生物特征數(shù)據(jù)存儲(chǔ)到 Keychain里,從而提高用戶數(shù)據(jù)的安全性。本文中我們將探尋如何使用Face ID 和 Touch ID來實(shí)現(xiàn)登錄認(rèn)證。
正文
創(chuàng)建一個(gè)工程,實(shí)現(xiàn)一個(gè)簡(jiǎn)單的登錄頁(yè)面(你也可以從這里下載 download), 運(yùn)行程序如圖所示:

打開這個(gè)工程,你需要把相應(yīng)的配置改成你自己的,如:

打開
LoginViewController.swift , 在 managedObjectContext:下添加幾個(gè)常量:
let usernameKey = "Batman"
let passwordKey = "Hello Bruce!"
這是我們內(nèi)置的用戶名和密碼,作為校驗(yàn)用戶的憑證。讓后我們添加一個(gè)登錄校驗(yàn)函數(shù):
func checkLogin(username: String, password: String) -> Bool {
return username == usernameKey && password == passwordKey
}
接下來實(shí)現(xiàn)一個(gè)登錄函數(shù), 函數(shù)內(nèi)部實(shí)現(xiàn):
if checkLogin(username: usernameTextField.text!, password: passwordTextField.text!) {
performSegue(withIdentifier: "dismissLogin", sender: self)
}
這樣一個(gè)簡(jiǎn)單的登錄實(shí)現(xiàn)就完成了。這種簡(jiǎn)單的用戶校驗(yàn)過程看起來是沒有什么問題,實(shí)際上我們的用戶信息(賬號(hào)和密碼)不應(yīng)該存儲(chǔ)在應(yīng)用程式中,這樣很容易被破解。因此我們可以將其存儲(chǔ)到Keychain中來提高其安全性。如果你想更多的了解Keychain,請(qǐng)移步 Basic Security in iOS 5 – Part 1.
Keychain
用 Keychain 如何存儲(chǔ)數(shù)據(jù)呢?蘋果官方提供了實(shí)例代碼GenericKeychain
在我們的工程中創(chuàng)建 KeychainPasswordItem.swift如:

思路:將用戶名和密碼存入如
Keychain 中,然后在根據(jù)用戶的輸入進(jìn)行校驗(yàn)。在使用
Keychain之前,你需要對(duì)應(yīng)用進(jìn)行配置保證數(shù)據(jù)可以寫入 Keychain中,然后打開LoginViewController.swift:
// Keychain Configuration
struct KeychainConfiguration {
static let serviceName = "TouchMeIn"
static let accessGroup: String? = nil
}
然后在 managedObjectContext:加入:
var passwordItems: [KeychainPasswordItem] = []
let createLoginButtonTag = 0
let loginButtonTag = 1
@IBOutlet weak var loginButton: UIButton!
我們?cè)诼暶饕粋€(gè)提醒登陸失敗的函數(shù):
private func showLoginFailedAlert() {
let alertView = UIAlertController(title: "Login Problem",
message: "Wrong username or password.",
preferredStyle:. alert)
let okAction = UIAlertAction(title: "Foiled Again!", style: .default)
alertView.addAction(okAction)
present(alertView, animated: true)
}
接下來我們登錄函數(shù)里的實(shí)現(xiàn)就可以改為:
@IBAction func loginAction(sender: UIButton) {
// 1
// Check that text has been entered into both the username and password fields.
guard let newAccountName = usernameTextField.text,
let newPassword = passwordTextField.text,
!newAccountName.isEmpty,
!newPassword.isEmpty else {
showLoginFailedAlert()
return
}
// 2
usernameTextField.resignFirstResponder()
passwordTextField.resignFirstResponder()
// 3
if sender.tag == createLoginButtonTag {
// 4
let hasLoginKey = UserDefaults.standard.bool(forKey: "hasLoginKey")
if !hasLoginKey && usernameTextField.hasText {
UserDefaults.standard.setValue(usernameTextField.text, forKey: "username")
}
// 5
do {
// This is a new account, create a new keychain item with the account name.
let passwordItem = KeychainPasswordItem(service: KeychainConfiguration.serviceName,
account: newAccountName,
accessGroup: KeychainConfiguration.accessGroup)
// Save the password for the new item.
try passwordItem.savePassword(newPassword)
} catch {
fatalError("Error updating keychain - \(error)")
}
// 6
UserDefaults.standard.set(true, forKey: "hasLoginKey")
loginButton.tag = loginButtonTag
performSegue(withIdentifier: "dismissLogin", sender: self)
} else if sender.tag == loginButtonTag {
// 7
if checkLogin(username: newAccountName, password: newPassword) {
performSegue(withIdentifier: "dismissLogin", sender: self)
} else {
// 8
showLoginFailedAlert()
}
}
}
我們的checkLogin(username:password:)函數(shù)的實(shí)現(xiàn)也改為:
func checkLogin(username: String, password: String) -> Bool {
guard username == UserDefaults.standard.value(forKey: "username") as? String else {
return false
}
do {
let passwordItem = KeychainPasswordItem(service: KeychainConfiguration.serviceName,
account: username,
accessGroup: KeychainConfiguration.accessGroup)
let keychainPassword = try passwordItem.readPassword()
return password == keychainPassword
} catch {
fatalError("Error reading password from keychain - \(error)")
}
}
刪除原來定義的常量代碼,實(shí)現(xiàn)viewDidLoad():
// 1
let hasLogin = UserDefaults.standard.bool(forKey: "hasLoginKey")
// 2
if hasLogin {
loginButton.setTitle("Login", for: .normal)
loginButton.tag = loginButtonTag
createInfoLabel.isHidden = true
} else {
loginButton.setTitle("Create", for: .normal)
loginButton.tag = createLoginButtonTag
createInfoLabel.isHidden = false
}
// 3
if let storedUsername = UserDefaults.standard.value(forKey: "username") as? String {
usernameTextField.text = storedUsername
}
這樣我們的Keychain存儲(chǔ)數(shù)據(jù)就完成了。
那么我們的 Touch ID呢?
搭建Touch ID界面(詳細(xì)過程略):

添加本地驗(yàn)證
Local Authentication庫(kù)為我們提供了實(shí)現(xiàn)生物特征識(shí)別的 Api ,我們?cè)陧?xiàng)目中導(dǎo)入 Local Authentication。官方文檔是這樣描述的:
“The Local Authentication framework provides facilities for requesting authentication from users with specified security policies.”
安全性提升到直接取決于用戶的——手指和臉。在 iOS 11中開始支持人臉識(shí)別, 你需要在工程的 info plist文件中聲明 Privacy – Face ID Usage Description:

創(chuàng)建
TouchIDAuthentication.swift文件:
import LocalAuthentication
enum BiometricType {
case none
case touchID
case faceID
}
class BiometricIDAuth {
let context = LAContext()
var loginReason = "Logging in with Touch ID"
func biometricType() -> BiometricType {
let _ = context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
switch context.biometryType {
case .none:
return .none
case .touchID:
return .touchID
case .faceID:
return .faceID
}
}
func canEvaluatePolicy() -> Bool {
return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
}
func authenticateUser(completion: @escaping (String?) -> Void) {
guard canEvaluatePolicy() else {
completion("Touch ID not available")
return
}
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: loginReason) { (success, evaluateError) in
if success {
DispatchQueue.main.async {
// User authenticated successfully, take appropriate action
completion(nil)
}
} else {
let message: String
switch evaluateError {
case LAError.authenticationFailed?:
message = "There was a problem verifying your identity."
case LAError.userCancel?:
message = "You pressed cancel."
case LAError.userFallback?:
message = "You pressed password."
case LAError.biometryNotAvailable?:
message = "Face ID/Touch ID is not available."
case LAError.biometryNotEnrolled?:
message = "Face ID/Touch ID is not set up."
case LAError.biometryLockout?:
message = "Face ID/Touch ID is locked."
default:
message = "Face ID/Touch ID may not be configured"
}
completion(message) }
}
}
}
你可以在這里下載完整項(xiàng)目project
iOS 安全指南
文獻(xiàn)
How To Secure iOS User Data: The Keychain and Biometrics – Face ID or Touch ID