在这边提供少量指纹和面容支付的基本思路,差异以及所遇到的坑。
我们重点是考虑如何保证支付的安全,首先一定不能本地存入使用户的支付密码,这样在人行(中国人民银行)来检查的时候是行不通的,而且直接存密码在任何时候都是下下策。
我们应该考虑在指纹验证通过后,如何和服务端进行安全交互:
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, )) {},以后可以不使用网上的判断系统的方法了。
iOS 11新添加了3个枚举,其实都是和以前的一样,只不过换了个名字:
LAErrorAuthenticationFailed, // 验证信息出错,这个时候会弹出localizedFallbackTitle的按钮 LAErrorUserCancel // 使用户取消验证LAErrorUserFallback // 使用户点击了手动输入密码的按钮LAErrorSystemCancel // 被系统取消 LAErrorPasscodeNotSet // 使用户没有设置密码(六位数字或者者四位数字那个)LAErrorTouchIDNotAvailable // 使用户设施不支持Touch ID LAErrorTouchIDNotEnrolled // 使用户没有录入Touch IDLAErrorTouchIDLockout // 使用户错误次数被锁住了,需要解锁LAErrorAppCancel // 在验证中被其余app终止LAErrorInvalidContext // 这个应该是LAContext本身出错了,我还没遇到过//这个没遇到过,预计没使用了,枚举值都变成-1004了,说明被舍弃了LAErrorNotInteractive //下面是iOS 11新添加的LAErrorBiometryNotAvailable //和LAErrorTouchIDNotAvailable一样枚举值都是-6LAErrorBiometryNotEnrolled //和LAErrorTouchIDNotEnrolled一样枚举值都是-7LAErrorBiometryLockout //和LAErrorTouchIDLockout一样枚举值都是-8LAPolicy一共有两个LAPolicyDeviceOwnerAuthenticationWithBiometrics和LAPolicyDeviceOwnerAuthentication,区别是:
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,其余系统正常。后续假如有其余的我还会补充。