1. 程式人生 > >【iOS】指紋(面容)支付基本邏輯和適配

【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一共有兩個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,其他系統正常。後續如果有其他的我還會補充。