目录
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、登录后关闭指纹登录,后台清空该账号绑定的指纹数据。
行者常至,为者常成!