JHHK

欢迎来到我的个人网站
行者常至 为者常成

指纹登录

目录

TouchId与FaceId

TouchId与FaceId苹果做了整合,共用一套API接口,使用起来也很方便,主要有两种安全策略

LAPolicyDeviceOwnerAuthenticationWithBiometrics
LAPolicyDeviceOwnerAuthentication

具体的说明,就不再赘述,看文章开头的两篇参考文章,说的很详细。

下面贴出封装的一个工具类,使用起来会更加方便:

LCBiometryTool.h文件

//生物识别成功回调,data指纹信息
typedef void(^LCBiometrySuccess)(LCBiometryModel * biometry);

//生物识别失败回调,errorDes具体失败信息
typedef void(^LCBiometryFail)(NSString * errorDes,NSInteger code);


@protocol BiometryDelegate <NSObject>

/// 获取当前设备支持的生物识别类型
-(LCBiometryType)getBiometryType;


///生物识别是否可用,可用返回nil,不可用返回error实例
-(NSError*)canUseBiometry;


/// 打开生物识别验证
/// @param successHandle 成功回调
/// @param failHandle 失败回调
-(void)openBiometryWithSuccess:(LCBiometrySuccess)successHandle fail:(LCBiometryFail)failHandle;


/// 打开生物识别验证(带密码)
/// @param successHandle 成功回调
/// @param failHandle 失败回调
-(void)openBiometryAndPasswordWithSuccess:(LCBiometrySuccess)successHandle fail:(LCBiometryFail)failHandle;


/// 查看生物识别失败原因
/// @param error 失败error
-(NSString*)errorHandle:(NSError*)error;

@end



@protocol BiometryKeychaiDelegate <BiometryDelegate>

/// 为某一个用户打开生物识别验证,打开成功会将信息存储到钥匙串
/// 存储的信息是LCBiometryModel
///LCBiometryModel.biometryData = data
///LCBiometryModel.isAllowBiometry = YES;
///
/// @param userId 用户id
/// @param successHandle 成功回调
/// @param failHandle 失败回调
-(void)openBiometryWithUserId:(NSString*)userId Success:(LCBiometrySuccess)successHandle fail:(LCBiometryFail)failHandle;



/// 关闭生物识别验证
/// 关闭成功会将钥匙串中存储的LCBiometryModel重置
///LCBiometryModel.biometryData = nil
///LCBiometryModel.isAllowBiometry = NO;
///
/// @param userId 用户id
/// @param successHandle 成功回调
/// @param failHandle 失败回调
-(void)closeBiometryWithUserId:(NSString*)userId Success:(LCBiometrySuccess)successHandle fail:(LCBiometryFail)failHandle;



/// 从钥匙串获取用户设置过的生物识别数据,为空代表没有设置过
/// @param userId 用户id
-(LCBiometryModel*)getBiometryInfoInKeychaiWithUserId:(NSString*)userId;

@end

@interface LCBiometryTool : NSObject<BiometryKeychaiDelegate>

-(instancetype)init;

@end

LCBiometryTool.m文件

#import "LCBiometryTool.h"
#import <LocalAuthentication/LocalAuthentication.h>
#import "LCKeychaiTool.h"

#define LCBiometryKeychainId(userId) [NSString stringWithFormat:@"come.LCClientDemo.bundleId_%@",userId]

@interface LCBiometryTool()
@property (nonatomic, assign) LAPolicy policy;//
@end

@implementation LCBiometryTool

-(instancetype)init{
    self = [super init];
    if (self) {
        self.policy = LAPolicyDeviceOwnerAuthenticationWithBiometrics;
//        self.policy = LAPolicyDeviceOwnerAuthentication;
    }
    
    
    
    return self;
}



#pragma mark - <BiometryDelegate>

-(LCBiometryType)getBiometryType{
    LAContext *context = [[LAContext alloc] init];
    NSError *error = nil;
    if ([context canEvaluatePolicy:_policy error:&error]) {
        if (@available(iOS 11.0,*)) {
            if (context.biometryType == LABiometryTypeTouchID) {//指纹
                return LCBiometryTypeTouchID;

            }else if (context.biometryType == LABiometryTypeFaceID){//人脸;
                return LCBiometryTypeFaceID;
            }else{
                return LCBiometryTypeNone;
            }
        }else if(@available(iOS 8.0,*)){//指纹
            return LCBiometryTypeTouchID;
        }else{
            return LCBiometryTypeNone;
        }
    }else{
        return LCBiometryTypeNone;
    }
}


-(NSError*)canUseBiometry{
    LAContext *context = [[LAContext alloc] init];
    NSError *error = nil;
    if ([context canEvaluatePolicy:_policy error:&error]) {
        return nil;
    }else{
        return error;
    }
}


-(void)openBiometryWithSuccess:(LCBiometrySuccess)successHandle fail:(LCBiometryFail)failHandle{
    
    [self __openBiometryWithPolicy:_policy
                            userId:nil
                           isAllow:NO
                           Success:successHandle
                              fail:failHandle];
}


-(void)openBiometryAndPasswordWithSuccess:(LCBiometrySuccess)successHandle fail:(LCBiometryFail)failHandle{
    
    [self __openBiometryWithPolicy:LAPolicyDeviceOwnerAuthentication
                            userId:nil
                           isAllow:NO
                           Success:successHandle
                              fail:failHandle];
}




-(NSString*)errorHandle:(NSError*)error{
    NSLog(@"验证失败:%@",error.description);
    
    NSString * errorReason = nil;
    
    switch (error.code) {
        case LAErrorAuthenticationFailed:{
            errorReason = @"授权失败";
            break;
        }case LAErrorUserCancel:{
            //用户取消验证Touch ID
            errorReason = @"您取消了验证";
            break;
        }case LAErrorUserFallback:{
//            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
//                //用户选择输入密码,切换主线程处理
//                NSLog(@"用户选择输入密码,切换主线程处理");
//            }];
            
            errorReason = @"您选择了输入密码";
            break;
        }case LAErrorSystemCancel:{
            //系统取消授权,如其他APP切入
            errorReason = @"系统取消授权";
            break;
        }case LAErrorPasscodeNotSet:{
            //系统未设置密码
           errorReason = @"请先设置手机系统密码";
            break;
        }case LAErrorTouchIDNotAvailable:{
            //Authentication could not start, because Touch ID is not available on the device.
            errorReason = @"指纹识别不可用";
            break;
        }case LAErrorTouchIDNotEnrolled:{
            //指纹未录入Authentication could not start, because Touch ID has no enrolled fingers.
            errorReason = @"指纹未录入";
            break;
        }case LAErrorTouchIDLockout:{
            errorReason = @"指纹失败次数超限,被锁";
            break;
        }case LAErrorAppCancel:{
            //app取消,比如正在验证的时候调用invalidate
            //You receive this error if you call the invalidate method while authentication is in process.
            //The app canceled authentication.You receive this error if you call the invalidate method while authentication is in process..
            errorReason = @"app cancle";
            break;
        }case LAErrorInvalidContext:{
            //上下文失效
            //The context was previously invalidated.
            //You invalidate a context by calling its invalidate method.
            errorReason = @"context invalid";
            break;
        }
//        case LAErrorBiometryNotAvailable:{//相当于LAErrorTouchIDNotAvailable
//            //设备Face ID不可用,例如未打开
//            NSLog(@"设备Touch ID不可用,例如未打开");
//            break;
//        }
//        case LAErrorBiometryNotEnrolled:{//相当于LAErrorTouchIDNotEnrolled
//            //设备Face ID用户未录入
//            NSLog(@"设备Touch ID不可用,用户未录入");
//            break;
//        }
//        case LAErrorBiometryLockout:{//相当于LAErrorTouchIDLockout
//            //设备Face ID失败次数过多,被锁
//            NSLog(@"设备Touch ID不可用,用户未录入");
//            break;
//        }
            
        case LAErrorNotInteractive:{
            //关闭了互动权限
            //context.interactionNotAllowed = yes;
            errorReason = @"interactionNotAllowed";
            break;
        }
//        case LAErrorWatchNotAvailable:{//手表
//
//            break;
//        }
        default:{
            errorReason = @"未知情况";
//            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
//                //其他情况,切换主线程处理
//                NSLog(@"其他情况,切换主线程处理");
//            }];
            break;
        }
    }
    return errorReason;
}


#pragma mark - <BiometryKeychaiDelegate>

-(void)openBiometryWithUserId:(NSString*)userId
                      Success:(LCBiometrySuccess)successHandle
                         fail:(LCBiometryFail)failHandle{
    
    [self __openBiometryWithPolicy:_policy
                            userId:userId
                           isAllow:YES
                           Success:successHandle
                              fail:failHandle];
}



-(void)closeBiometryWithUserId:(NSString*)userId
                       Success:(LCBiometrySuccess)successHandle
                          fail:(LCBiometryFail)failHandle{
    
    [self __openBiometryWithPolicy:_policy
                            userId:userId
                           isAllow:NO
                           Success:successHandle
                              fail:failHandle];
}


-(LCBiometryModel*)getBiometryInfoInKeychaiWithUserId:(NSString*)userId{
    NSString * key = LCBiometryKeychainId(userId);
    LCBiometryModel * biometry = [LCKeychaiTool loadWithService:key];
    return biometry;
}





#pragma mark - 内部方法

-(void)__openBiometryWithPolicy:(LAPolicy)policy
                         userId:(nullable NSString*)userId
                        isAllow:(BOOL)isAllow
                        Success:(LCBiometrySuccess)successHandle
                           fail:(LCBiometryFail)failHandle{
    
    //IOS11之后如果支持faceId也是走同样的逻辑,faceId和TouchId只能选一个
    LAContext *context = [[LAContext alloc] init];
    NSError *error = nil;
    
    if ([context canEvaluatePolicy:policy error:&error]) {
        
        //只有在canEvaluatePolicy:返回yes时才会有数据
        //NSData * evalueData =  context.evaluatedPolicyDomainState;
        
        //配置显示文案
        if (@available(iOS 10.0, *)) {
            context.localizedCancelTitle = @"x取消x";
        }
        
       
        //该项设置为空,就会隐藏该项
//        context.localizedFallbackTitle = @"";
         context.localizedFallbackTitle = @"x使用其它方式x";
        
        //localizedReason
        NSString * localizedReason = @"x请验证已有指纹x";
        
        //开启验证
        __weak typeof(self) weakSelf = self;
        [context evaluatePolicy:policy localizedReason:localizedReason reply:^(BOOL success, NSError * _Nullable error) {
            if (success) {
                
                [weakSelf __successHandleWithData:context.evaluatedPolicyDomainState
                                           userId:userId
                                  isAllowBiometry:isAllow
                                          success:successHandle];
                
            }else{
                
                [weakSelf __failHandleWith:error fail:failHandle];
            }
        }];
    } else {
        [self __failHandleWith:error fail:failHandle];
    }
}


-(void)__successHandleWithData:(NSData*)evalueData
                        userId:(nullable NSString*)userId
               isAllowBiometry:(BOOL)isAllow
                       success:(LCBiometrySuccess)successHandle{
    
    //组装数据
    LCBiometryModel * biometry = [[LCBiometryModel alloc] init];
    biometry.isAllowBiometry = isAllow;
    biometry.biometryData = evalueData;
    
    if (userId.length && isAllow) {
        //存取标记:bundleId_userId,区分app和具体用户
        NSString * key = LCBiometryKeychainId(userId);
        @synchronized ([self class]) {
            [LCKeychaiTool saveWithService:key data:biometry];
        }
    }
    
    if (userId.length && !isAllow) {
        //存取标记:bundleId_userId,区分app和具体用户
        NSString * key = LCBiometryKeychainId(userId);
        @synchronized ([self class]) {
            biometry.biometryData = (id)[NSNull null];
            [LCKeychaiTool saveWithService:key data:biometry];
        }
    }

    
    if (successHandle) {
        dispatch_async(dispatch_get_main_queue(), ^{
            successHandle(biometry);
        });
    }
}


-(void)__failHandleWith:(NSError*)error
                   fail:(LCBiometryFail)failHandle{
    
    if (failHandle){
        dispatch_async(dispatch_get_main_queue(), ^{
            failHandle([self errorHandle:error],error.code);
        });
    }
}

@end

指纹登录

一、不与后台交互

只是简单的起到阻挡的作用,用户账号处于登录状态,当程序退入后台再进入前台(或者进入后台一定时间)时推出指纹识别,如果识别不成功,可退出登录后使用账号密码登录。

二、与后台交互

实现Touch ID 或 Face ID登录账号功能

1、第一次使用密码登录,登录成功后可选择开通指纹登录。若没有开通指纹登录就使用指纹进行登录时提示用户未开通,请使用密码登录后进行设置。

2、密码登录成功后用户进行指纹登录开通时,进行指纹验证,通过后将指纹数据(指纹数据最好再加上APP信息和个人账户信息进行融合,保证唯一性)传至后台与登录账号进行绑定。

3、若开通了指纹登录,当再次登录账号时可选择指纹登录,指纹验证成功后上送指纹信息,后台进行比对,比对一致登录成功返回token,比对不一致提示用户指纹信息发生更改,请使用密码登录后重新设置指纹信息。

4、登录后关闭指纹登录,后台清空该账号绑定的指纹数据。


行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.