指纹(面容)登录或支付

目前App登录方式主要有:

  • 数字密码登录
  • 手势登录
  • 指纹登录
  • 声音识别
  • 人脸识别(刷脸登录)
  • 虹膜识别

苹果从 iPhone 5s 开始支持指纹识别,iPhone X 开始支持面容识别,从 iOS 8 开始,指纹(面容)的 api 开始对开发者开放,指纹识别与面容识别共用同一套 api。

据 Apple 官方声称,指纹识别是生物真皮层识别,只能识别活着的生物,而且按压式的指纹识别有着更高的识别率和识别速度,面容识别是通过 iPhone X 的前置深感相机 3D 扫描用户脸部特征。

iPhone 指纹(面容)识别所需的环境:

  • iPhone 5s及以上设备,且指纹识别速度是随着设备逐步提升的;
  • iOS 8.0及以上系统版本;如果要做指纹支付,需要注意指纹库删减更换的漏洞,因此需要监测指纹库的Hash值(用于判断指纹库是否有变化),需要iOS 9以上版本;
  • 设备状态亦需支持;包括Touch ID未损坏,系统开启数字密码,需要提前录入系统指纹等。

指纹系统 API 介绍

1、 首先导入包含指纹识别的类 #import <LocalAuthentication/LocalAuthentication.h>

2、 判断设备是否支持指纹识别的接口canEvaluatePolicy:

- (BOOL)canEvaluatePolicy:(LAPolicy)policy error:(NSError * __autoreleasing *)error __attribute__((swift_error(none)));

3、 调起指纹登录窗口,进行指纹识别的接口evaluatePolicy:

- (void)evaluatePolicy:(LAPolicy)policy
       localizedReason:(NSString *)localizedReason
                 reply:(void(^)(BOOL success, NSError * __nullable error))reply;

指纹的识别错误处理

指纹识别,识别成功只有一种可能,而识别失败情况很多,所以主要需要处理的是各种失败的情况。

一、 判断设备是否支持Touch ID

在很多情况下,都需要先判断系统是否支持指纹,调用系统APIcanEvaluatePolicy:判断设备是否支持,以下为几种常见情况:

  • 系统指纹未录入;
  • 系统未设置密码;
  • 指纹设备损坏不可用;
  • 设备版本低于iOS 8.0;
  • 系统临时禁用指纹或者面容;
  • iPhone X 以上设备用户可手动关闭 app 面容。

提示1:如果连续几次输入错误,系统会临时禁用指纹识别,此时返回的也是 NO,直到正确输入一次系统密码会自动启用Touch ID;不同iOS版本,处理方式可能有所不同。 提示2:iPhone X 以上设备,用户可在 app 权限设置界面手动关闭面容。

二、 判断设备是否支持Touch ID的示例代码

可采用代码封装的方式,封装系统Api,然后根据返回的错误值不同,有选择的分类处理失败的情况即可。

    //创建LAContext
    LAContext* context = [[LAContext alloc] init];
    NSError* error = nil;

    //首先使用canEvaluatePolicy 判断设备支持状态
    if (![context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]){
        //不支持指纹识别的三种情况,失败可采用Block回调的方式,把错误信息传递过去
        switch (error.code) {
            case LAErrorTouchIDNotEnrolled:
            {
                dispatch_async(dispatch_get_main_queue(), ^{
                    //错误原因说明:(@"指纹未录入或系统未设置密码");
                });
                break;
            }
            case LAErrorPasscodeNotSet:
            {
                dispatch_async(dispatch_get_main_queue(), ^{
                    //错误原因说明:(@"系统未设置密码");
                });
                break;
            }
            default:
            {
                dispatch_async(dispatch_get_main_queue(), ^{
                    //错误原因说明:(@"指纹不可用");
                });

                break;
            }
        }
    }else{
        //iOS8.0后才支持指纹识别接口
        if ([UIDevice currentDevice].systemVersion.floatValue < 8.0) {
            错误原因:(@"系统版本低,不支持");
        }else{
            指纹可用,可以调起指纹登录弹窗
        }
    }

调用指纹验证弹窗

在调用弹窗时,仍然需要注意错误处理,个别特殊情况仍会出现系统不支持指纹的情况,例如多次验证失败会临时禁用,用户删除了系统密码等。 调用系统APIevaluatePolicy:主要会出现的情况:

  • 指纹未录入或系统未设置密码
  • 系统未设置密码
  • 系统版本低,不支持
  • 指纹验证取消(包括用户点击取消按钮,系统中断取消(例如接到来电,其他App切入))
  • 指纹验证几次后失败
  • 系统未设置密码
  • 设备指纹不可用
  • 用户选择点击指纹弹窗右边按钮

指纹弹窗窗口说明:

  • 指纹登录窗口为系统窗口,且优先级很高,几乎能够覆盖到其他任何窗口的上面。
  • 指纹验证的回调reply默认在子线程执行,如果需要刷新UI,一定要切换到主线程执行,否则会有卡顿
  • 如果指纹弹出有明显延迟卡顿现象,注意查看系统版本是否为iOS 10.1,此为系统版本原因,对比其他App和其他系统版本即可发现。

指纹弹窗按钮设置

  • 通过设置LAContext的localizedFallbackTitle字符串,可设置弹窗下方的选择按钮
  • 其中指纹弹窗第一次弹出时,只有一个取消按钮,验证失败1次后才显示你设置的按钮,按钮标题为你设置的字符串
  • 如果不设置,在输错错误的指纹之后会显示“输入密码”选项,点击会提示输入系统密码
  • 如果连续输入错误,依然不想显示除取消外的其他按钮,可设置context.localizedFallbackTitle = @"";即可。

指纹库的Hash值

  • 如果安全级别要求较高,防止有人知道手机系统密码,添加自己的指纹到指纹库,从而进行App的操作,可在开启指纹时,指纹验证成功后,保存指纹库的Hash值。
  • 苹果一向提倡保护用户隐私,此Hash值记录的并不是用户指纹,而是当前指纹库的Hash值,即记录的是指纹库整体的特征,如果指纹库有删除/增加/更改等任何变化,该Hash值都会变化。
  • 感兴趣的小伙伴,可以查询一下Hash值,Hash值和MD5也经常用来校验文件的完整性。
  • 注意的是,Hash值为一串字符串,且在iOS 9.0之后才支持,所以如果用到此值,你App的指纹登录/支付功能需要判断系统版本>9.0

用户设置按钮的回调

其中一种验证失败的情况为LAErrorUserFallback,此为用户点击了设置的context.localizedFallbackTitle,可根据自身需要,切换到手势或数字密码等。


指纹验证弹窗示例代码:

可采用代码封装的方式,封装系统Api,然后根据返回的错误值不同,有选择的分类处理失败的情况即可。

//创建LAContext
    LAContext* context = [[LAContext alloc] init];
    //localizedFallbackTitle 默认不设置的话,在输错错误的指纹之后会显示“输入密码”选项,如果出入空字符串@""将不显示这个选项
    context.localizedFallbackTitle = fallbackTitle;
    
    //验证指纹识别
    [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:Subtitle reply:^(BOOL success, NSError * _Nullable error) {
        if (success) {
            //如果安全级别要求较高,可在此处保存当前的指纹库的hash值为全局临时变量
            //此临时变量为指纹库的hash值,如果指纹有变化,则用其他方式登录成功后,更新服务器为此最新的指纹信息
            //比较,注意:evaluatedPolicyDomainState是在iOS9后出现,故指纹hash需要在iOS9后才能使用
            context.evaluatedPolicyDomainState;
            //验证成功,则执行刷新UI,主线程执行
            dispatch_async(dispatch_get_main_queue(), ^{
                //刷新UI,主线程执行
            });
            
        }else{
            //验证失败
            switch (error.code) {
                case LAErrorSystemCancel:
                {
                    //系统取消授权,如其他APP切入,接到电话等情况
                    dispatch_async(dispatch_get_main_queue(), ^{
                        //错误原因说明:(@"指纹验证取消");
                    });
                    break;
                }
                case LAErrorUserCancel:
                {
                    //用户取消验证Touch ID;
                    //⚠️坑:iOS9增加invalidate接口,可用代码取消弹窗,iOS9版本之前通过代码无法使弹窗消失
                    dispatch_async(dispatch_get_main_queue(), ^{
                        //错误原因说明:(@"指纹验证取消");
                    });
                    break;
                }
                case LAErrorAuthenticationFailed:
                {
                    //授权失败,3次尝试验证后,授权失败调用
                    dispatch_async(dispatch_get_main_queue(), ^{
                        //错误原因说明:(@"指纹验证失败");
                    });
                    
                    break;
                }
                case LAErrorPasscodeNotSet:
                {
                    //系统未设置密码
                   dispatch_async(dispatch_get_main_queue(), ^{
                        //错误原因说明:(@"系统未设置密码");
                    });
                    break;
                }
                case LAErrorTouchIDNotAvailable:
                {
                    //设备Touch ID不可用,例如未打开
                    dispatch_async(dispatch_get_main_queue(), ^{
                        //错误原因说明:(@"设备指纹不可用");
                    });
                    break;
                }
                case LAErrorTouchIDNotEnrolled:
                {
                    //设备Touch ID不可用,用户未录入
                    //指纹未录入或系统未设置密码
                    dispatch_async(dispatch_get_main_queue(), ^{
                        //错误原因说明:(@"指纹未录入或系统未设置密码");
                    });
                    break;
                }
                case LAErrorUserFallback:
                {
                    //用户选择手动输入密码,根据右边的文字判断怎么跳转
                    dispatch_async(dispatch_get_main_queue(), ^{
                        //错误原因说明:(fallbackTitle);
                    });
                    break;
                }
                default:
                {
                    //例如连续验证指纹失败验证手机密码是,用户取消验证
                    dispatch_async(dispatch_get_main_queue(), ^{
                        //错误原因说明:(@"指纹验证失败");
                    });
                    break;
                }
            }
        }
    }];

如果您觉得有所帮助,请在GitHub上赏个Star ⭐️,您的鼓励是我前进的动力