【iOS】指紋(面容)支付基本邏輯和適配
在這邊提供一些指紋和麵容支付的基本思路,差異以及所遇到的坑。
一、支付邏輯基本思路
我們重點是考慮如何保證支付的安全,首先肯定不能本地存入使用者的支付密碼,這樣在人行(中國人民銀行)來檢查的時候是行不通的,而且直接存密碼在任何時候都是下下策。
我們應該考慮在指紋驗證通過後,如何和服務端進行安全互動:
1、首先指紋或者面容通過後,我們需要和服務端進行安全環境校驗,這個目的是保證當前的環境是安全的。可參考的方式有兩種,第一種是用RSA加密,公鑰加密私鑰解密。,使用規定的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,其他系統正常。後續如果有其他的我還會補充。