【iOS】指纹(面容)支付基本逻辑和适配

在这边提供一些指纹和面容支付的基本思路,差异以及所遇到的坑。

一、支付逻辑基本思路

我们重点是考虑如何保证支付的安全,首先肯定不能本地存入用户的支付密码,这样在人行(中国人民银行)来检查的时候是行不通的,而且直接存密码在任何时候都是下下策。
我们应该考虑在指纹验证通过后,如何和服务端进行安全交互:
1、首先指纹或者面容通过后,我们需要和服务端进行安全环境校验,这个目的是保证当前的环境是安全的。可参考的方式有两种,第一种是用RSA加密,公钥加密私钥解密。第二种是AES加密,使用规定的key进行加解密。
2、安全环境校验通过后,再将所需的字段拼接加密后传给服务端进行校验,校验通过即可支付。这里所需的字段有一点,就是一定要保证唯一性,可以是设备id,用户id组合成的唯一id,类似于token。

二、适配面容支付

iPhoneX出了Face ID,因此我们也需要对Face ID进行适配(虽然我觉得不太安全,没有指纹有安全感)。
1、在系统API调用方面,其实是一样的,只是需要区分下何时是指纹何时是面容,以便进行图片、文字的更换,系统API提供了一个LABiometryType枚举进行类型判断,代码如下:

  LAContext *context  = [[LAContext alloc]init];
  NSError *authError = nil;
// MARK: 判断设备是否支持指纹识别
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]){
    NSString *myLocalizedReasonString;
    //系统给的系统判断API
    if (@available(iOS 11.0, *)) {
        if(context.biometryType == LABiometryTypeTouchID) {
            myLocalizedReasonString = @"请按Home键验证指纹";
        }else if (context.biometryType == LABiometryTypeFaceID){
            myLocalizedReasonString = @"请验证面容 ";
        }else{
            myLocalizedReasonString = @"请按Home键验证指纹";
        }
    }
  //iOS 11以前没有面容
    else{
        myLocalizedReasonString = @"请按Home键验证指纹";
    };
}

这里需要注意的是一定要先用 if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError])进行判断,然后再用LABiometryType取出验证类型,这在很多文章里都没有提过,也是我在开发过程中遇到的问题。还有一点就是系统提供了一个写法来判断方法支持最低的iOS系统: if (@available(iOS 11.0, )) {},以后可以不用网上的判断系统的方法了。

三、LAError枚举值说明

iOS 11新增了3个枚举,其实都是和以前的一样,只不过换了个名字:

LAErrorAuthenticationFailed, // 验证信息出错,这个时候会弹出localizedFallbackTitle的按钮  
LAErrorUserCancel // 用户取消验证
LAErrorUserFallback // 用户点击了手动输入密码的按钮
LAErrorSystemCancel // 被系统取消
 LAErrorPasscodeNotSet // 用户没有设置密码(六位数字或者四位数字那个)
LAErrorTouchIDNotAvailable // 用户设备不支持Touch ID 
LAErrorTouchIDNotEnrolled // 用户没有录入Touch ID
LAErrorTouchIDLockout // 用户错误次数被锁住了,需要解锁
LAErrorAppCancel // 在验证中被其他app终止
LAErrorInvalidContext // 这个应该是LAContext本身出错了,我还没遇到过
//这个没遇到过,估计没用了,枚举值都变成-1004了,说明被舍弃了
LAErrorNotInteractive 
//下面是iOS 11新增的
LAErrorBiometryNotAvailable //和LAErrorTouchIDNotAvailable一样枚举值都是-6
LAErrorBiometryNotEnrolled //和LAErrorTouchIDNotEnrolled一样枚举值都是-7
LAErrorBiometryLockout //和LAErrorTouchIDLockout一样枚举值都是-8

四、LAPolicy枚举值说明

LAPolicy一共有两个LAPolicyDeviceOwnerAuthenticationWithBiometricsLAPolicyDeviceOwnerAuthentication,区别是:
1、LAPolicyDeviceOwnerAuthentication是iOS 9之后的;
2、LAPolicyDeviceOwnerAuthentication是在指纹面容验证失败后(5次)第6次弹出锁屏密码验证,如果验证成功了就可以认定指纹或者面容成功了。
3、LAPolicyDeviceOwnerAuthenticationWithBiometrics则是失败5次后,第6次指纹或者面容就被锁定了,我们需要在第6次解锁指纹或者面容,代码如下:

case LAErrorTouchIDLockout:{
             dispatch_async(dispatch_get_main_queue(), ^{
                [context evaluatePolicy:kLAPolicyDeviceOwnerAuthentication localizedReason:@"验证手机锁屏密码,解锁指纹" reply:^(BOOL success, NSError * _Nullable error){
                    if (success) {
                       //重新进行指纹校验方法调用
                    }
                }];
            });
            
            break;
        }

需要注意的是用swich遍历LAError的时候,操作需要在主线程进行。

五、指纹验证示例整段代码

#pragma mark - 验证指纹
- (void)loadAuthentication{
    // 这个属性是设置指纹输入失败之后的弹出框的选项
    LAContext *context  = [[LAContext alloc]init];
    context.localizedFallbackTitle = @"输入密码";
    NSError *authError = nil;

    // MARK: 判断设备是否支持指纹识别
   
    if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]){
    NSString *myLocalizedReasonString;
    if (@available(iOS 11.0, *)) {
        if(context.biometryType == LABiometryTypeTouchID) {
            myLocalizedReasonString = @"请按Home键验证指纹";
        }else if (context.biometryType == LABiometryTypeFaceID){
            myLocalizedReasonString = @" 请验证面容ID";
        }else{
            myLocalizedReasonString = @"请按Home键验证指纹";
        }
    }
    else{
        myLocalizedReasonString = @"请按Home键验证指纹";
    };
    [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:myLocalizedReasonString reply:^(BOOL success, NSError * _Nullable error) {
        
        if(success){
            //在主线程进行
            dispatch_async(dispatch_get_main_queue(), ^{
                //认证成功
                //进行支付
            });
            
        }else{
            dispatch_async(dispatch_get_main_queue(), ^{
                
            });
            switch (error.code){
                case LAErrorAuthenticationFailed:{
                    // -1 连续三次指纹识别错误
                    dispatch_async(dispatch_get_main_queue(), ^{
                       //认证失败,请输入支付密码支付
                    });
                    break;
                }
                    
                case LAErrorUserFallback:{
                    // -3 用户选择其他验证方式
                    (点击了 context.localizedFallbackTitle = @"输入密码"这里的响应)
                    //主线程
                    dispatch_async(dispatch_get_main_queue(), ^{
                        
                    });
                    break;
                }
                    
                    // Authentication could not start, because passcode is not set on the device.
                    // -5 设备系统未设置密码
                    //没有面容ID权限(-6)
                case LAErrorTouchIDNotAvailable:{
                    dispatch_async(dispatch_get_main_queue(), ^{
                       //引导用户跳转到设置去开启
                        
                    });
                    break;
                }
                //iPhone没设置密码  
                case LAErrorPasscodeNotSet :{
                    dispatch_async(dispatch_get_main_queue(), ^{
                       //引导用户跳转到设置去开启
                        
                    });
                    break;
                }
                  //iPhone没录入指纹 
                case LAErrorTouchIDNotEnrolled:{
                    dispatch_async(dispatch_get_main_queue(), ^{
                       //引导用户跳转到设置去录入
                        
                    });
                    break;
                }
              //用户连续多次进行Touch ID验证失败,Touch ID被锁,需要用户输入密码解锁,先Touch ID验证密码
                case LAErrorTouchIDLockout:{
                    // -8 连续五次指纹识别错误,TouchID功能被锁定,下一次需要输入系统密码
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [context evaluatePolicy:kLAPolicyDeviceOwnerAuthentication localizedReason:@"验证手机锁屏密码,解锁指纹" reply:^(BOOL success, NSError * _Nullable error){
                            if (success) {
                                [self loadAuthentication];
                            }
                        }];
                    });
                    break;
                }
                    
                default:{
                  
                    break;
                }
            }
        }
    }];
}else{

    switch (authError.code){
        //没有面容ID权限(-6)
        case LAErrorTouchIDNotAvailable:{
            dispatch_async(dispatch_get_main_queue(), ^{
                //引导用户跳转到设置去开启
               
            });
            break;
        }
        case LAErrorAuthenticationFailed:{
            // -1 连续三次指纹识别错误
            dispatch_async(dispatch_get_main_queue(), ^{
               
                //认证失败,请输入支付密码支付
            });
            break;
        }
            
        case LAErrorPasscodeNotSet :{
            dispatch_async(dispatch_get_main_queue(), ^{
                //引导用户跳转到设置去开启

            });
            break;
        }
        case LAErrorTouchIDNotEnrolled:{
            dispatch_async(dispatch_get_main_queue(), ^{
                //引导用户跳转到设置去开启

            });
            break;
        }
            
        case LAErrorTouchIDLockout:{
            dispatch_async(dispatch_get_main_queue(), ^{
                [context evaluatePolicy:kLAPolicyDeviceOwnerAuthentication localizedReason:@"验证手机锁屏密码,解锁指纹" reply:^(BOOL success, NSError * _Nullable error){
                    if (success) {
                        [self loadAuthentication];
                    }
                }];
            });
            
            break;
        }
        
        default:{
            dispatch_async(dispatch_get_main_queue(), ^{
                if (@available(iOS 11.0, *)) {
                    if(context.biometryType == LABiometryTypeTouchID) {
                       //设备不支持Touch ID
                    }else if (self.myContext.biometryType == LABiometryTypeFaceID){
                        //设备不支持面容 ID
                    }else{
                      //设备不支持Touch ID
                    }
                }else{
                    //设备不支持Touch ID
                }
            });
            break;
        }
      }
    }
  }

五、总结

具体的指纹支付逻辑,加密方式,还是应该和服务端以及安卓一起讨论决定。其他就是做好异常处理,保证代码安全。
目前我还遇到了一个奇怪的bug,就是iOS 11.0系统,用户录入了指纹,但是没有将指纹或者密码用于锁屏解锁,就会每次都跳出LAErrorTouchIDLockout这个错误。试了一下,微信和支付宝一样的,也就是系统bug,其他系统正常。后续如果有其他的我还会补充。

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

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,960评论 3 119
  • 搬了新家,退了店铺。当一切归复平静的时候,突然感觉一切又要从头开始新的抉择!人生仿佛又站在了十字路口,等待...
    鸿韵莲心阅读 485评论 0 0
  • 外婆家的村落--肖坊村。 肖坊村位于江西省吉安市吉安县官田乡平田村西北部,距乡政府所在地5公里,全村现有农户26户...
    贵哥撩悟阅读 245评论 0 2
  • 20岁是个界限,好像一到这个点你就什么都懂了。 这个星期很快,一转眼,搜的一下,又到了周六,这个星期可能因为要考试...
    筱筱格子阅读 412评论 0 0
  • reads 1 和 reads 2 去掉adapter,程序一样,且from SJ reads2 去掉前6bp和1...
    koda阅读 1,310评论 0 1