iOS指纹使用

现在大多数需要用户校验的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不会弹出系统解锁密码输入界面,需要手动再调用一次指纹。我这边的处理是直接弹出自己的交易密码。

参考文章
1、http://www.jianshu.com/p/aef5a506311b

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • 简述: 在类似支付宝为首的应用以及各种理财等涉及钱财对安全性要求较高的应用中,目前普遍对关键数据都做了安全访问限制...
    C_HPY阅读 2,862评论 0 18
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,609评论 4 59
  • 谈过一场恋爱就像经历过一场感冒样 分开的一段时间对爱情有免疫力 但后来 又是没有办法的落入爱情
    唐唐charming阅读 272评论 1 1
  • 听说,有一种顶部像一朵伞的形状的树,在五月、六月间会开出艳红色袋红晕的花,在远处看来格外艳丽夺目,这种花称为毕业花...
    蒋小玉阅读 639评论 0 5
  • 当我听到(拥抱你离去)这首歌的时候,心理却十分的感慨,因为这首歌唱出了爱情本身的意义和现实。 又当我听到(爱过你还...
    久孤__阅读 5,431评论 1 2