现在大多数需要用户校验的APP都会加上指纹校验,方便简洁。
应用场景
该指纹应用到交易场景中,类似于支付宝的支付密码以及指纹密码。因为目前苹果指纹验证成功之后并不会返回任何信息,因此验证需要服务端的配置,一下是应用中指纹校验流程
指纹校验流程
关键代码
1、自己创建vc,然后调用getFingerCode即可,参数needFinger代表是否立即进行指纹校验,可以在viewDidLoad中调用
//
// USTouchIDManager.swift
// NewUstock
//
// Created by amus on 2017/4/11.
// Copyright © 2017年. All rights reserved.
//
import Foundation
import LocalAuthentication
import Toast_Swift
import RxSwift
import KeychainAccess
open class USTouchIDManager: NSObject {
// public static let instance = USTouchIDManager() // 做成单例block会有问题
// 解绑类型: 绑定,解锁, 解绑
enum TouchIDInputType: Int{
case bind
case unlock
case unbind
}
fileprivate static let kKeyChainService = "com.amus.touchid" // keychain service
fileprivate static let kAPPTouchIDCode = "kAPPTouchIDCode" // 保存在keychain中的
fileprivate static var isShowing = false // 指纹识别是否已经显示出来
public static var isSetCode: Variable<Bool> = Variable(false) // 是否设置
fileprivate var cryptFingerCode: String? = nil
fileprivate var unbindBlock: (() -> Void)?
public var validatedHandler: ((Bool) -> (Void))? // 指纹验证成功之后回调
public var pretendServerFingerCode = "bfd434_22ufddnf" // 假装成后台返回的,其实是应该后台返回的,与用户相关
/// 单例、外部不允许创建
// override init() {
// super.init()
// }
/// 指纹是否可用
///
/// - Returns: 是否可用
public static func isTouchIDAvaraible() -> Bool {
let context = LAContext()
var authError: NSError? = nil
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) {
return true
} else {
guard let error = authError as? LAError else {
return true
}
return self.dealTouchErrorInfo(error: error)
}
}
/// 有可能被锁定导致指纹不可用
///
/// - Parameter error: 类型
/// - Returns: 指纹是否可用
fileprivate static func dealTouchErrorInfo(error: LAError) -> Bool {
switch error.code {
case .passcodeNotSet,
.touchIDNotAvailable,
.touchIDNotEnrolled:
return false
default:
return true
}
}
/// 获取指纹code,获取之后根据参数进行一次指纹绑定
/// 该方法的功能主要是返回后台的指纹code,然后保存在keychain中
///
/// - Parameter needFinger: 获取之后是否进行指纹识别,默认不进行
public func getFingerCode(_ needFinger: Bool = false) {
// 网络处理,这里做相关的描述
/*
1、获取后台的指纹code,一段与用户相关的字符串编码信息
2、将获取的code进行md5加密
*/
let fingerCode = self.pretendServerFingerCode // 假装后台返回了
self.cryptFingerCode = USTouchIDManager.getCryptFingerCode(fingerCode)
if needFinger {
self.bindTouckID()
}
}
/// MARK: - 绑定指纹
func bindTouckID() {
guard let code = self.cryptFingerCode else {
return
}
if USTouchIDManager.isShowing {
return
}
let context = LAContext()
context.localizedFallbackTitle = ""
var authError: NSError? = nil
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) {
USTouchIDManager.isShowing = true
UI.topViewController()?.view.makeToastActivity(.center)
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "验证指纹", reply: { (success, error) in
USTouchIDManager.isShowing = false
DispatchQueue.main.async {
UI.topViewController()?.view.hideToastActivity()
if success {
self.bindFingerCode(pwd: "", fingerCode: code)
}
else {
UI.topViewController()?.view.hideToastActivity()
guard let error = error as? LAError else {
return
}
self.dealErrorInfo(error: error, type: .bind)
}
}
})
}
else {
guard let error = authError as? LAError else {
return
}
DispatchQueue.main.async {
self.dealErrorInfo(error: error, type: .bind)
}
}
}
/// 绑定指纹信息,这里需要跟后台一起操作
///
/// - Parameters:
/// - pwd: 之前输入的密码
/// - fingerCode: 加密后的指纹code
public func bindFingerCode(pwd: String, fingerCode: String){
/*
1、调用后台接口,传递指纹验证之前的密码验证,这一步也可以不要,还需要服务端返回的指纹code
2、后台接口调用成功之后,将传递给后台的fingerCode保存在keychain中
USTouchIDManager.saveFingerCode(fingerCode)
*/
UI.topViewController()?.view.makeToast("绑定成功")
}
/// MARK: - 验证指纹
/// 校验指纹解锁
///
/// - Parameter code: 指纹code
public func validateTouchID(_ code: String) {
if USTouchIDManager.isShowing {
return
}
let context = LAContext()
context.localizedFallbackTitle = "交易密码"
if #available(iOS 10.0, *) {
context.localizedCancelTitle = "取消"
}
var authError: NSError? = nil
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) {
USTouchIDManager.isShowing = true
UI.topViewController()?.view.makeToastActivity(.center)
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "验证指纹", reply: { (success, error) in
USTouchIDManager.isShowing = false
DispatchQueue.main.async {
UI.topViewController()?.view.hideToastActivity()
if success {
self.loginWithFingerCode(code)
}
else {
guard let error = error as? LAError else {
return
}
self.dealErrorInfo(error: error, type: .unlock)
}
}
})
}
else {
guard let error = authError as? LAError else {
return
}
DispatchQueue.main.async {
self.dealErrorInfo(error: error, type: .unlock)
}
}
}
/// 校验指纹,更新tradeToken
///
/// - Parameter code: 指纹code
fileprivate func loginWithFingerCode(_ code: String) {
/*
1、调用后台接口,校验指纹
2、校验完成之后可以调用block,这样在调用改类的地方可以用block做一些事情
*/
UI.topViewController()?.view.makeToast("校验成功")
}
/// 指纹解绑成功回掉
///
/// - Parameter unbindBlock: block
public func unbindSuccess(_ unbindBlock: @escaping () -> Void) {
self.unbindBlock = unbindBlock
}
/// 解绑指纹, 可以使用交易密码解绑 -- 该方法不再使用,使用密码解绑
/// 这个方法可以不需要了,解绑指纹,直接使用其他方式,可以是交易密码,也可以是直接点击,也可以是手机号解绑
public func unbindTouchID() {
let context = LAContext();
context.localizedFallbackTitle = "交易密码"
if #available(iOS 10.0, *) {
context.localizedCancelTitle = "取消"
}
var authError: NSError? = nil;
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) {
USTouchIDManager.isShowing = true
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "验证指纹", reply: { (success, error) in
USTouchIDManager.isShowing = false
DispatchQueue.main.async {
if success {
USTouchIDManager.clearFingerCode()
}
else {
guard let error = error as? LAError else {
return
}
self.dealErrorInfo(error: error, type: .unbind)
}
}
})
}
else {
guard let error = authError as? LAError else {
return
}
DispatchQueue.main.async {
self.dealErrorInfo(error: error, type: .unbind)
}
}
}
/// 处理指纹识别,或者另一个按钮的点击
///
/// - Parameters:
/// - error: 错误信息
/// - type: 指纹处理类型
@discardableResult
fileprivate func dealErrorInfo(error: LAError, type: TouchIDInputType = TouchIDInputType.unlock) -> Bool {
var flag = false
switch error.code {
case .authenticationFailed:
//SSLog("身份验证多次失败: 因为用户未能提供有效身份证件.")
self.dealAuthenticationFailed(type: type)
flag = true
case .userCancel:
//SSLog("身份验证被用户取消: (例如: 点击 [取消] 按钮).")
flag = true
case .userFallback:
// 输入密码后续操作
// .deviceOwnerAuthenticationWithBiometrics 模式下点击输入密码才会触发此错误
//SSLog("身份验证被取消: 因为用户在 \"首次验证失败后\" 的 \"第二次验证中\" 点击了 [输入密码] 按钮.")
//USTradePasswordAlertView().show()
// 这里会弹出指纹的输入密码(这个按钮自定义的文字,操作也在这里自定义)
flag = true
case .systemCancel:
// SSLog("身份验证被系统取消: (例如: 另一个应用程序准备切换到前台).")
flag = true
case .passcodeNotSet:
// SSLog("身份验证无法启动: 因为没有在设备上设置密码 (只有设置设备的锁屏密码, 才能开启 Touch ID).")
break
case .touchIDNotAvailable:
// SSLog("身份验证无法启动: 因为 Touch ID 不可用 (例如: Touch ID 损坏、设备没有指纹识别硬件模块...).")
break
case .touchIDNotEnrolled:
// SSLog("身份验证无法启动: 因为没有设置指纹.")
break
case .touchIDLockout:
// SSLog("身份验证失败: 因为多次尝试失败, Touch ID 被锁定, 需要通过验证锁屏密码来重新启用 Touch ID.")
self.dealTouchIDLockout(type: type)
flag = true
case .appCancel:
// SSLog("身份验证被 App 取消")
flag = true
default:
flag = true
}
return flag
}
/// 处理指纹失败三次或者多次的情况
///
/// - Parameter type: 进行指纹操作的类型
fileprivate func dealAuthenticationFailed(type: TouchIDInputType = .unlock) {
switch type {
case .bind, .unbind:
break
case .unlock:
// 指纹验证多次失败处理
// USTradePasswordAlertView().show()
break
}
}
/// 处理指纹锁定需要手机密码的情况,密码输入成功,也算指纹成功(这里可以自己处理)
fileprivate func dealTouchIDLockout(type: TouchIDInputType = .unlock) {
switch type {
case .bind, .unbind:
let action1 = UIAlertAction(title: "取消", style: .cancel, handler: nil)
let alert: UIAlertController = UIAlertController(title: "Touch ID 被锁定", message: "Touch ID 已经被锁定,请去设置中打开Touch ID", preferredStyle: .alert)
alert.addAction(action1)
UI.topViewController()?.us.present(alert)
case .unlock:
let action1 = UIAlertAction(title: "取消", style: .cancel, handler: nil)
let action2 = UIAlertAction(title: "交易密码", style: .default, handler: { (action) in
// 交易密码的处理,touchid锁定之后,需要其他的方式解锁
})
let alert = UIAlertController(title: "Touch ID 被锁定", message: "Touch ID 已经被锁定,请去设置中打开Touch ID或者输入密码", preferredStyle: .alert)
alert.addAction(action1)
alert.addAction(action2)
UI.topViewController()?.us.present(alert)
break
}
}
}
// MARK: - 指纹相关操作
extension USTouchIDManager {
/// 根据给定的code获取md5加密后的code
///
/// - Parameter originCode: 原始code
/// - Returns: 加密后的code
fileprivate static func getCryptFingerCode(_ originCode: String) -> String? {
guard let cryptoCode = Utils.password(originCode) else {
UI.topViewController()?.view.makeToast("请勿在密码中添加特殊字符")
return nil
}
return cryptoCode
}
/// 获取保存在本地的指纹code,已经md5加密
///
/// - Returns: 指纹code
public static func getKeychainFingerCode() -> String? {
let keychain = Keychain(service: kKeyChainService)
return keychain[kAPPTouchIDCode]
}
/// 使用md5加密后保存在keychain之中的fingercode
///
/// - Parameter code: 加密后的fingerCode
public static func saveFingerCode(_ code: String) {
let keychain = Keychain(service: kKeyChainService)
keychain[kAPPTouchIDCode] = code
self.isSetCode.value = true
}
/// 清除用户指纹信息
public static func clearFingerCode() {
let keychain = Keychain(service: kKeyChainService)
keychain[kAPPTouchIDCode] = nil
self.isSetCode.value = false
}
}
注意事项
1、iOS指纹识别采用的是3+2验证,第一次验证未通过,允许用户设置一个确定按钮,确定按钮可以用来输入自己的交易密码,也可以调用系统的解锁密码。如果5次都输入失败,iOS10不会弹出系统解锁密码输入界面,需要手动再调用一次指纹。我这边的处理是直接弹出自己的交易密码。