【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一共有两个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,其他系统正常。后续如果有其他的我还会补充。