指纹登录TouchID&FaceID

2,696 阅读3分钟

随着智能手机越来越方便,人们打开手机的方式也多起来,先是指纹登录,现在又有了face id登录,以后还不一定有什么登录了,可能是感应你的气味等等...

基础配置

#import <LocalAuthentication/LocalAuthentication.h>
  • 判断是否支持指纹登录
 //首先判断版本
if (NSFoundationVersionNumber < NSFoundationVersionNumber_iOS_8_0) {
      NSLog(@"系统版本不支持TouchID");
      return;
}

指纹登录的类

  • LocalAuthentication
    • LAContext
    • LAError
    • LAErrorDomain
  • LAContext
    • 继承NSObject
    • Checking Availability
      • canEvaluatePolicy:error:// 使用canEvaluatePolicy方法判断设备是否支持TouchID,返回BOOL为YES,该设备支持TouchID。
      • LAPolicy
      • biometryType
      • LABiometryType
    • Evaluating Authentication Policies
      • evaluatePolicy:localizedReason:reply:// 方法是对TouchID进行验证,Block回调中如果success为YES则验证成功,为NO验证失败,并对error进行解析.
      • evaluatedPolicyDomainState// 用于判断设备上的指纹是否被更改,在LAContext被创建的时候,evaluatedPolicyDomainState才生效,可在TouchID验证成功时,将它记录下来,用于下次使用TouchID时校验,提高安全性。
    • Evaluating Access Controls
      • evaluateAccessControl:operation:localizedReason:reply:
      • LAAccessControlOperation
      • interactionNotAllowed
    • Customizing Authentication Prompts
      • localizedReason
      • localizedFallbackTitle
      • localizedCancelTitle
    • Reusing Device Unlock State
      • touchIDAuthenticationAllowableReuseDuration
      • LATouchIDAuthenticationMaximumAllowableReuseDuration
    • Managing Credentials
      • setCredential:type:
      • isCredentialSet:
      • LACredentialType
    • Invalidating the Authentication Context
      • invalidate
  • LAPolicy
    • Policies
      • LAPolicyDeviceOwnerAuthenticationWithBiometrics // 是支持iOS8以上系统,使用该设备的TouchID进行验证,当输入TouchID验证5次失败后,TouchID被锁定,只能通过锁屏后解锁设备时输入正确的解锁密码来解锁TouchID。
      • LAPolicyDeviceOwnerAuthenticationWithWatch
      • LAPolicyDeviceOwnerAuthenticationWithBiometricsOrWatch
      • LAPolicyDeviceOwnerAuthentication // 是支持iOS9以上系统,使用该设备的TouchID或设备密码进行验证,当输入TouchID验证5次失败后,TouchID被锁定,会触发设备密码页面进行验证。
    • Policy Constants
      • kLAPolicyDeviceOwnerAuthenticationWithBiometrics
      • kLAPolicyDeviceOwnerAuthenticationWithWatch
      • kLAPolicyDeviceOwnerAuthenticationWithBiometricsOrWatch
      • kLAPolicyDeviceOwnerAuthentication
  • 生物识别(LAPolicyDeviceOwnerAuthenticationWithBiometrics)
    • Touch ID:首先弹出指纹识别的弹窗,当第一次指纹验证失败时,弹框会变成两个按钮:其中一个是“取消”,另一个按钮可以自定义标题和点击事件,自定义标题的变量为:localizedFallbackTitle;自定义点击事件,需要在错误回调中,拿到错误码为:kLAErrorUserFallback时,进行我们的点击事件即可。当3次指纹验证失败时,验证弹框会消失,此时还可以继续呼出验证弹框进行验证,若接下来的两次指纹识别都验证错误的话,Touch ID会锁住。
    • Face ID:当设备支持Face ID时,会调用Face ID的验证弹框,需要注意的是,面容识别是错误一次时,需要点击“再次尝试面容ID”按钮,才能继续验证,若第2次再次错误时,会弹出自定义标题按钮,和Touch ID的相同。5次识别错误后,Face ID也会被锁住。
  • 生物识别 + 密码认证(LAPolicyDeviceOwnerAuthentication)
    • Touch ID:如果3次识别错误后,则会弹出系统密码输入验证页面,需要输入设备的密码来解锁。如果此时取消,还有2次调用指纹识别进行验证,如果都失败的话,接下来每次调用识别,都是用系统密码进行验证。
    • Face ID:当连续5次验证错误,即5次需要输入密码时,Face ID会被锁住,无法使用,需要进行系统密码验证过后来能继续使用。
  • LABiometryType
    • 该属性是用于判断当前设备支持的验证类型,我们可以拿到该属性判断支持Touch ID还是Face ID,从而自定义提示文字或者是localizedReason的文字。
    • 对于LABiometryType,苹果给出的一段注释需要特别注意:只有当调用了canEvaluatePolicy方法,返回是true并且没有错误的情况下才会设置该值,错误是指之前所说的NSErrorPointer,因此canEvaluatePolicy的第二个error参数不要设置为nil。在调用canEvaluatePolicy方法之前,或者调用后有error的情况下,该属性均无任何意义的值。
    • 因为是iOS 11新增的,因此在使用时,需要先判断当前的系统。#available(iOS 11.0, *)
    • LABiometryTypeNone
    • LABiometryTypeFaceID
    • LABiometryTypeTouchID
public enum LABiometryType : Int {

    /// 此设备不支持生物识别.
    @available(iOS 11.2, *)
    case none

    /// The device does not support biometry.
    @available(iOS, introduced: 11.0, deprecated: 11.2, renamed: "LABiometryType.none")
    public static var LABiometryNone: LABiometryType { get }

    /// 此设备支持 Touch ID.
    case touchID

    /// 此设备支持 Face ID.
    case faceID
}
  • LAError
typedef NS_ENUM(NSInteger, LAError)
{
    /// 身份验证不成功,因为用户无法提供有效的凭据
    LAErrorAuthenticationFailed = kLAErrorAuthenticationFailed,
    
    /// 身份验证被用户取消 (e.g. 点击了取消按钮).
    LAErrorUserCancel = kLAErrorUserCancel,
    
    /// 身份验证被取消了,因为用户点击了返回按钮 (输入密码).
    LAErrorUserFallback = kLAErrorUserFallback,
    
    /// 身份验证被系统取消了 (e.g. 另外一个应用到了前台/当前应用退到了后台).
    LAErrorSystemCancel = kLAErrorSystemCancel,
    
    /// 验证无法启动,因为设备没有设置密码.
    LAErrorPasscodeNotSet = kLAErrorPasscodeNotSet,

    /// 身份验证无法启动,因为Touch ID在该设备不可用.
    LAErrorTouchIDNotAvailable NS_ENUM_DEPRECATED(10_10, 10_13, 8_0, 11_0, "use LAErrorBiometryNotAvailable") = kLAErrorTouchIDNotAvailable,

    /// 身份验证无法启动,因为Touch ID没有录入指纹.
    LAErrorTouchIDNotEnrolled NS_ENUM_DEPRECATED(10_10, 10_13, 8_0, 11_0, "use LAErrorBiometryNotEnrolled") = kLAErrorTouchIDNotEnrolled,

    /// 身份验证不成功,因为有太多失败的Touch ID尝试
    /// Touch ID现在是锁着的,解锁Touch ID必须使用密码, e.g. 调用
    /// LAPolicyDeviceOwnerAuthenticationWithBiometrics 的时候,输入密码是必要条件.
    LAErrorTouchIDLockout NS_ENUM_DEPRECATED(10_11, 10_13, 9_0, 11_0, "use LAErrorBiometryLockout")
        __WATCHOS_DEPRECATED(3.0, 4.0, "use LAErrorBiometryLockout") __TVOS_DEPRECATED(10.0, 11.0, "use LAErrorBiometryLockout") = kLAErrorTouchIDLockout,

    /// 应用程序取消了身份验证 (e.g. 在进行身份验证时,调用了invalidate).
    LAErrorAppCancel NS_ENUM_AVAILABLE(10_11, 9_0) = kLAErrorAppCancel,

    /// LAContext 传递给这个调用时,已经失效.
    LAErrorInvalidContext NS_ENUM_AVAILABLE(10_11, 9_0) = kLAErrorInvalidContext,

    /// 身份验证无法启动,因为生物识别在当前设备上不可用.
    LAErrorBiometryNotAvailable NS_ENUM_AVAILABLE(10_13, 11_0) __WATCHOS_AVAILABLE(4.0) __TVOS_AVAILABLE(11.0) = kLAErrorBiometryNotAvailable,

    /// 身份验证无法启动,因为生物识别没有录入信息.
    LAErrorBiometryNotEnrolled NS_ENUM_AVAILABLE(10_13, 11_0) __WATCHOS_AVAILABLE(4.0) __TVOS_AVAILABLE(11.0) = kLAErrorBiometryNotEnrolled,

    /// 身份验证不成功,因为太多次的验证失败并且生物识别验证处理锁定状态,必须输入密码才能解锁.
    LAErrorBiometryLockout NS_ENUM_AVAILABLE(10_13, 11_0) __WATCHOS_AVAILABLE(4.0) __TVOS_AVAILABLE(11.0) = kLAErrorBiometryLockout,
    
    /// 身份验证失败, 因为需要显示的UI界面使用了interactionNotAllowed属性
    LAErrorNotInteractive API_AVAILABLE(macos(10.10), ios(8.0), watchos(3.0), tvos(10.0)) = kLAErrorNotInteractive,
} NS_ENUM_AVAILABLE(10_10, 8_0) __WATCHOS_AVAILABLE(3.0) __TVOS_AVAILABLE(10.0);

Face ID

  • 如果是支持Face ID,需要在plist文件中,添加NSFaceIDUsageDescription。虽然不添加,也不会导致闪退或者其他问题,但是苹果在API文档中,还是标明了需要添加该key。

Applications should also supply NSFaceIDUsageDescription key in the Info.plist. This key identifies a string value that contains a message to be displayed to users when the app is trying to use Face ID for the first time. Users can choose to allow or deny the use of Face ID by the app before the first use or later in Face ID privacy settings. When the use of Face ID is denied, evaluations will fail with LAErrorBiometryNotAvailable.

应用场景

场景1:指纹登录01

在设备(用户)绑定之后,并且用户账号退出后,可以使用指纹登录,若当前设备未绑定,则不会出现“指纹登录”按钮。(以下前提是app已经绑定指纹登录)

  • 第一步:打开APP,提示使用指纹登录
  • 第二步:验证TouchID:检测当前设备是否支持TouchID(如果之前绑定过指纹登录,肯定是支持的),若支持则发起TouchID验证(比较你的指纹和录入的指纹是否一致);
  • 登录:读取app在本机的设备账号/密码,调用设备登录接口,发起登录请求;
  • 成功:验证设备账号/密码后,返回相应状态,登录成功则完成整个TouchID登录流程。

场景2:指纹绑定02

在设备第一次使用指纹登录之前,必须先登录原有的账号(app注册账号),进行设备(用户)绑定。

  • 第一步:在app登录后,到设置界面,点击开启“指纹登录”;
  • 第二步:验证TouchID:检测当前设备是否支持TouchID,若支持则发起TouchID验证;
  • 第三步:生成设备账号/密码:TouchID验证通过后,根据当前已登录的账号和硬件设备Token,生成设备账号/密码(规则可自定,密码要长要复杂),并保存在keychain;
  • 绑定:生成设备账号/密码后,将原账号及设备账号/密码,加密后(题主使用的是RSA加密)发送到服务端进行绑定;
  • 成功:验证原账号及设备账号有效后,返回相应状态,绑定成功则完成整个TouchID(设备)绑定流程。

参考代码

- (IBAction)loginButtonClick:(UIButton *)sender {

    //首先判断版本
    if (NSFoundationVersionNumber < NSFoundationVersionNumber_iOS_8_0) {
        NSLog(@"系统版本不支持TouchID");
        return;
    }

    LAContext *context = [[LAContext alloc] init];
    context.localizedFallbackTitle = @"输入密码";
    if (@available(iOS 10.0, *)) {
         context.localizedCancelTitle = @"22222";
    } else {
        // Fallback on earlier versions
    }
    NSError *error = nil;

    if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {

        [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:@"通过Home键验证已有手机指纹" reply:^(BOOL success, NSError * _Nullable error) {

            if (success) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    NSLog(@"TouchID 验证成功");
                });
            }else if(error){

                switch (error.code) {
                    case LAErrorAuthenticationFailed:{
                        dispatch_async(dispatch_get_main_queue(), ^{
                            NSLog(@"TouchID 验证失败");
                        });
                        break;
                    }
                    case LAErrorUserCancel:{
                        dispatch_async(dispatch_get_main_queue(), ^{
                            NSLog(@"TouchID 被用户手动取消");
                        });
                    }
                        break;
                    case LAErrorUserFallback:{
                        dispatch_async(dispatch_get_main_queue(), ^{
                            NSLog(@"用户不使用TouchID,选择手动输入密码");
                        });
                    }
                        break;
                    case LAErrorSystemCancel:{
                        dispatch_async(dispatch_get_main_queue(), ^{
                            NSLog(@"TouchID 被系统取消 (如遇到来电,锁屏,按了Home键等)");
                        });
                    }
                        break;
                    case LAErrorPasscodeNotSet:{
                        dispatch_async(dispatch_get_main_queue(), ^{
                            NSLog(@"TouchID 无法启动,因为用户没有设置密码");
                        });
                    }
                        break;
                    case LAErrorTouchIDNotEnrolled:{
                        dispatch_async(dispatch_get_main_queue(), ^{
                            NSLog(@"TouchID 无法启动,因为用户没有设置TouchID");
                        });
                    }
                        break;
                    case LAErrorTouchIDNotAvailable:{
                        dispatch_async(dispatch_get_main_queue(), ^{
                            NSLog(@"TouchID 无效");
                        });
                    }
                        break;
                    case LAErrorTouchIDLockout:{
                        dispatch_async(dispatch_get_main_queue(), ^{
                            NSLog(@"TouchID 被锁定(连续多次验证TouchID失败,系统需要用户手动输入密码)");
                        });
                    }
                        break;
                    case LAErrorAppCancel:{
                        dispatch_async(dispatch_get_main_queue(), ^{
                            NSLog(@"当前软件被挂起并取消了授权 (如App进入了后台等)");
                        });
                    }
                        break;
                    case LAErrorInvalidContext:{
                        dispatch_async(dispatch_get_main_queue(), ^{
                            NSLog(@"当前软件被挂起并取消了授权 (LAContext对象无效)");
                        });
                    }
                        break;
                    default:
                        break;
                }
            }
        }];

    }else{
        NSLog(@"当前设备不支持TouchID");
    }
}

代码参考2

/**
 * 判断设备是否支持指纹识别
 */
- (IBAction)loginBtnAction:(UIButton *)sender
{

    [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:YES] forKey:@"loginState"];

    EVNHelper *helper = [EVNHelper shareHelper];
    helper.isAppCurrentLoginState = YES;

    LAContext *context = [[LAContext alloc] init]; // 初始化上下文对象
    NSError *error = nil;
    // 判断设备是否支持指纹识别功能
    if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error])
    {
        // 支持指纹验证
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"登录成功!" message:@"是否启用指纹登录" preferredStyle:UIAlertControllerStyleAlert];

        __weak typeof (self) weakSelf = self;

        UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"稍后" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {

            [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:NO] forKey:@"startAutoLoginState"];
            weakSelf.transLoginStateBlock(); // 回传
            [self dismissViewControllerAnimated:YES completion:nil];
        }];

        UIAlertAction *startUseAction = [UIAlertAction actionWithTitle:@"启用" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {

            [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:YES] forKey:@"startAutoLoginState"];
            weakSelf.transLoginStateBlock(); // 回传
            [self dismissViewControllerAnimated:YES completion:nil];

        }];
        [alertController addAction:cancelAction];
        [alertController addAction:startUseAction];
        
        [self presentViewController:alertController animated:YES completion:nil];
    }
    else
    {
        [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:NO] forKey:@"startAutoLoginState"];
        self.transLoginStateBlock(); // 回传
        [self dismissViewControllerAnimated:YES completion:nil];
    }

}


/**
 * 指纹登录验证
 */
- (void)loadAuthentication
{
    __weak typeof(self) weakSelf = self;

    LAContext *myContext = [[LAContext alloc] init];
    // 这个属性是设置指纹输入失败之后的弹出框的选项
    myContext.localizedFallbackTitle = @"忘记密码";

    NSError *authError = nil;
    NSString *myLocalizedReasonString = @"请按住Home键完成验证";
    // MARK: 判断设备是否支持指纹识别
    if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError])
    {
        [myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:myLocalizedReasonString reply:^(BOOL success, NSError * _Nullable error) {
            if(success)
            {
                NSLog(@"指纹认证成功");

                weakSelf.helper.isAppCurrentLoginState = YES;

                weakSelf.logoutBtnAction.hidden = NO;
                weakSelf.userInfo.text = @"仁伯安";
            }
            else
            {
                weakSelf.helper.isAppCurrentLoginState = NO;
                NSLog(@"指纹认证失败,%@",error.description);

                NSLog(@"%ld", (long)error.code); // 错误码 error.code
                switch (error.code)
                {
                    case LAErrorAuthenticationFailed: // Authentication was not successful, because user failed to provide valid credentials
                    {
                        NSLog(@"授权失败"); // -1 连续三次指纹识别错误
                    }
                        break;
                    case LAErrorUserCancel: // Authentication was canceled by user (e.g. tapped Cancel button)
                    {
                        NSLog(@"用户取消验证Touch ID"); // -2 在TouchID对话框中点击了取消按钮

                    }
                        break;
                    case LAErrorUserFallback: // Authentication was canceled, because the user tapped the fallback button (Enter Password)
                    {
                        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                            NSLog(@"用户选择输入密码,切换主线程处理"); // -3 在TouchID对话框中点击了输入密码按钮
                        }];

                    }
                        break;
                    case LAErrorSystemCancel: // Authentication was canceled by system (e.g. another application went to foreground)
                    {
                        NSLog(@"取消授权,如其他应用切入,用户自主"); // -4 TouchID对话框被系统取消,例如按下Home或者电源键
                    }
                        break;
                    case LAErrorPasscodeNotSet: // Authentication could not start, because passcode is not set on the device.

                    {
                        NSLog(@"设备系统未设置密码"); // -5
                    }
                        break;
                    case LAErrorTouchIDNotAvailable: // Authentication could not start, because Touch ID is not available on the device
                    {
                        NSLog(@"设备未设置Touch ID"); // -6
                    }
                        break;
                    case LAErrorTouchIDNotEnrolled: // Authentication could not start, because Touch ID has no enrolled fingers
                    {
                        NSLog(@"用户未录入指纹"); // -7
                    }
                        break;

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0
                    case LAErrorTouchIDLockout: //Authentication was not successful, because there were too many failed Touch ID attempts and Touch ID is now locked. Passcode is required to unlock Touch ID, e.g. evaluating LAPolicyDeviceOwnerAuthenticationWithBiometrics will ask for passcode as a prerequisite 用户连续多次进行Touch ID验证失败,Touch ID被锁,需要用户输入密码解锁,先Touch ID验证密码
                    {
                        NSLog(@"Touch ID被锁,需要用户输入密码解锁"); // -8 连续五次指纹识别错误,TouchID功能被锁定,下一次需要输入系统密码
                    }
                        break;
                    case LAErrorAppCancel: // Authentication was canceled by application (e.g. invalidate was called while authentication was in progress) 如突然来了电话,电话应用进入前台,APP被挂起啦");
                    {
                        NSLog(@"用户不能控制情况下APP被挂起"); // -9
                    }
                        break;
                    case LAErrorInvalidContext: // LAContext passed to this call has been previously invalidated.
                    {
                        NSLog(@"LAContext传递给这个调用之前已经失效"); // -10
                    }
                        break;
#else
#endif
                    default:
                    {
                        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                            NSLog(@"其他情况,切换主线程处理");
                        }];
                        break;
                    }
                }
            }
        }];
    }
    else
    {
        NSLog(@"设备不支持指纹");
        NSLog(@"%ld", (long)authError.code);
        weakSelf.helper.isAppCurrentLoginState = NO;
        switch (authError.code)
        {
            case LAErrorTouchIDNotEnrolled:
            {
                NSLog(@"Authentication could not start, because Touch ID has no enrolled fingers");
                break;
            }
            case LAErrorPasscodeNotSet:
            {
                NSLog(@"Authentication could not start, because passcode is not set on the device");
                break;
            }
            default:
            {
                NSLog(@"TouchID not available");
                break;
            }
        }
    }
}