原创内容,转载引用请注明来源
最近公司需求,需要集成生物识别,于是开始研究了一下生物识别,总体来说很简单,这里主要目的是记录一下。的苹果的生物识别用到一个 LocalAuthentication 类,通过这个类可以进行生物识别(touchID 和 faceID)的唤起和使用,注意在使用之前需要在info里面主题添加相应key:Privacy - Face ID Usage Description,否者大家都知道。由于我们当前产品端还没有提出具体的需求,我这里只是提前做好,所以当下只说在我们项目中的思路和步骤。
我在项目中自定义一个生物识别的管理类,用来管理生物识别,对外提供三项服务:
- 提供判断当前设备是否支持生物识别。
- 提供当前设备支持的生物识别类型,即面容识别或者是指纹识别,或者是不支持(没有设置密码 或者硬件损坏等)。
- 唤起生物识别,并通过代理返回识别结果
详细步骤:前提工作把代理、识别结果的枚举、识别类型的枚举准备好,后面在详细编写工具类代码
一、生物识别代理
/// 生物识别代理
public protocol ZTBiometricDelegate: NSObject{
/// 代理方法
func handleBiometricResult(result:ZTBiometricResult,type:ZTBiometricType)
}
二、生物识别结果枚举,这里我直接列出来了,详情请看下面代码
/// 生物识别结果枚举
public enum ZTBiometricResult{
// --- 自定义 ---
/// 解锁成功
case success
/// 设备不支持 - 版本过低
case versionNotSupport
/// 设备不支持 - 硬件已损坏
case deviceNotSupport
// --- 通用 ---
/// 验证失败 - 提示指纹不匹配 可再次点击(还没有达到最大错误次数)
case authenticationFailed
/// 用户取消【通常-可点击按钮再次触发指纹/面容解锁】
case userCancel
/// 用户选择使用密码 - 【通常-跳转到登录页使用密码进行解锁】
case userFallback
/// 系统取消 - 保持在TouchID解锁界面,点击按钮重新弹出TouchID进行解锁
case systemCancel
/// 用户没有设置密码,无法使用TouchID/FaceID
case passcodeNotSet
// --- iOS 11+ ---
/// TouchID/FaceID的硬件不可用
case biometryNotAvailable
/// 用户没有设置TouchID/FaceID,无法使用
case biometryNotEnrolled
/// 面容识别错误达到一定次数,被锁定
case biometryLockout
// --- iOS 9+ ---
/// TouchID错误达到了一定次数,被锁定
case touchIDLockout
/// 被应用取消,比如当身份验证正在进行时,调用了invalidate
case appCancel
/// 传入的LAContext已经无效,如应用被挂起已取消了授权
case invalidContext
// --- iOS 8+ ---
/// TouchID的硬件不可用
case touchIdNotAvailable
/// 指纹识别未开启
case touchIDNotEnrolled
}
三、生物识别类型枚举
/// 生物识别类型
public enum ZTBiometricType{
case touchID //指纹识别
case faceID //面容识别
case none //无 || 不支持
}
四、生物识别工具类
/// 单例
static let shared = ZTBiometricManager()
let ZTContext:LAContext = LAContext()
public weak var resultDelegate: ZTBiometricDelegate?
public var localizedFallbackTitle = "输入密码"
public var localizedReason = "西门吸雪需进行生物验证"
4.1:判断生物识别是否可用
/// 判断生物识别是否可用
/// - Returns: 是否可用
public func biometricAvaliable() -> Bool{
if #available(iOS 8.0, *) {
var error: NSError?
return ZTContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
} else {
return false
}
}
4.2:获取生物识别类型
/// 获取生物识别类型
/// - Returns: 类型
public func biometricType()->ZTBiometricType{
if #available(iOS 8.0, *){
if ZTTool.isBangScreen() { return .faceID }else{ return .touchID }
}else{
return .none
}
}
4.3:唤起生物识别
/// 唤起生物识别
public func evokeBiometric(){
var error: NSError?
guard #available(iOS 8.0, *) else { resultDelegate?.handleBiometricResult(result: .versionNotSupport, type: biometricType()); return }//版本过低
// 此处使用canEvaluatePolicy进行判断,巧妙的在使用deviceOwnerAuthenticationWithBiometrics方式被锁定后捕获错误
// 在LAErrorHandle中通过使用deviceOwnerAuthentication立马触发系统密码以达到解除锁定的目的
guard ZTContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else { ZTBiometricErrorHandle(errorCode: error?.code ?? 0, context: ZTContext); return }
//这里开始真正的唤起生物识别//deviceOwnerAuthenticationWithBiometrics
ZTContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: localizedReason, reply: { (success, evaluateError) in
DispatchQueue.main.async { [self] in
if success {
self.resultDelegate?.handleBiometricResult(result: .success, type: biometricType())
} else {
if let error = evaluateError as NSError? {
self.ZTBiometricErrorHandle(errorCode: error.code, context: ZTContext)
}
}
}
})
}
4.4:这里对识别错误的多种情况进行处理
/// 私有方法:生物识别错误信息回调
/// - Parameters:
/// - errorCode: 错误码
/// - context: context
private func ZTBiometricErrorHandle(errorCode: Int, context: LAContext) {
if errorCode == LAError.authenticationFailed.rawValue {
resultDelegate?.handleBiometricResult(result: .authenticationFailed, type: biometricType())
} else if errorCode == LAError.userCancel.rawValue {
resultDelegate?.handleBiometricResult(result: .userCancel, type: biometricType())
} else if errorCode == LAError.userFallback.rawValue {
resultDelegate?.handleBiometricResult(result: .userFallback, type: biometricType())
} else if errorCode == LAError.systemCancel.rawValue {
resultDelegate?.handleBiometricResult(result: .systemCancel, type: biometricType())
} else if errorCode == LAError.passcodeNotSet.rawValue {
resultDelegate?.handleBiometricResult(result: .passcodeNotSet, type: biometricType())
} else { // 判断iOS系统版本,高版本包含低版本,保证低设备型号运行高iOS版本的正确错误捕获
if #available(iOS 11.0, *) {
// --- iOS 11+ ---
if errorCode == LAError.biometryNotAvailable.rawValue {
resultDelegate?.handleBiometricResult(result: .biometryNotAvailable, type: biometricType())
} else if errorCode == LAError.biometryNotEnrolled.rawValue {
resultDelegate?.handleBiometricResult(result: .biometryNotEnrolled, type: biometricType())
} else if errorCode == LAError.biometryLockout.rawValue {
// 面容识别错误达到一定次数被锁定;换用deviceOwnerAuthentication「生物识别+密码认证」可立马触发系统密码框解锁【关键处理】
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: localizedReason, reply: { (success, error) in
})
}
// --- iOS 9+ ---
else if errorCode == LAError.touchIDLockout.rawValue {
// 指纹识别错误达到一定次数被锁定;换用deviceOwnerAuthentication「生物识别+密码认证」可立马触发系统密码框解锁【关键处理】
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: localizedReason, reply: { (success, error) in
})
} else if errorCode == LAError.appCancel.rawValue {
resultDelegate?.handleBiometricResult(result: .appCancel, type: biometricType())
} else if errorCode == LAError.invalidContext.rawValue {
resultDelegate?.handleBiometricResult(result: .invalidContext, type: biometricType())
}
// --- iOS 8+ ---
else if errorCode == LAError.touchIDNotAvailable.rawValue {
resultDelegate?.handleBiometricResult(result: .touchIdNotAvailable, type: biometricType())
} else if errorCode == LAError.touchIDNotEnrolled.rawValue {
resultDelegate?.handleBiometricResult(result: .touchIDNotEnrolled, type: biometricType())
}
// --- 硬件不支持指纹或面容解锁(或设备损坏)所有的错误类型都枚举完了且iOS版本检验也过,只能是硬件设备不支持了 ---
else {
resultDelegate?.handleBiometricResult(result: .deviceNotSupport, type: biometricType())
}
} else if #available(iOS 9.0, *) {
// --- iOS 9+ ---
if errorCode == LAError.touchIDLockout.rawValue {
// 指纹识别错误达到一定次数被锁定;换用deviceOwnerAuthentication「生物识别+密码认证」可立马触发系统密码框解锁【关键处理】
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: localizedReason, reply: { (success, error) in
})
} else if errorCode == LAError.appCancel.rawValue {
resultDelegate?.handleBiometricResult(result: .appCancel, type: biometricType())
} else if errorCode == LAError.invalidContext.rawValue {
resultDelegate?.handleBiometricResult(result: .invalidContext, type: biometricType())
}
// --- iOS 8+ ---
else if errorCode == LAError.touchIDNotAvailable.rawValue {
resultDelegate?.handleBiometricResult(result: .touchIdNotAvailable, type: biometricType())
} else if errorCode == LAError.touchIDNotEnrolled.rawValue {
resultDelegate?.handleBiometricResult(result: .touchIDNotEnrolled, type: biometricType())
}
// --- 不支持指纹或面容解锁(或设备损坏) ---
else {
resultDelegate?.handleBiometricResult(result: .deviceNotSupport, type: biometricType())
}
} else { // iOS 9以前的系统,此处省略了#available(iOS 8.0, *)判断,该判断前面已做
// --- iOS 8+ ---
if errorCode == LAError.touchIDNotAvailable.rawValue {
resultDelegate?.handleBiometricResult(result: .touchIdNotAvailable, type: biometricType())
} else if errorCode == LAError.touchIDNotEnrolled.rawValue {
resultDelegate?.handleBiometricResult(result: .touchIDNotEnrolled, type: biometricType())
}
// --- 不支持指纹或面容解锁(或设备损坏) ---
else {
resultDelegate?.handleBiometricResult(result: .deviceNotSupport, type: biometricType())
}
}
}
}