flutter开发实战-指纹、面容ID验证插件实现
在iOS开发中,经常出现需要指纹、面容ID验证的功能。
指纹、面容ID是一种基于用生物识别技术,通过扫描用户的面部特征来验证用户身份。
一、效果图
二、iOS指纹、面容ID验证
在iOS中实现指纹、面容ID验证功能,步骤如下
2.1 info.plist配置
在info.plist中配置允许访问FACE ID权限
<key>NSFaceIDUsageDescription</key>
<string>允许设备访问Face ID</string>
2.2 引入LocalAuthentication
在代码中引入#import <LocalAuthentication/LocalAuthentication.h>
具体代码实现如下
SDAuthID.h
#import <Foundation/Foundation.h>
#import <LocalAuthentication/LocalAuthentication.h>
/**
* 设备支持的生物验证方式
*/
typedef NS_ENUM(NSUInteger, SDAuthIDSupportType) {
/**
* 支持TouchID验证
*/
SDAuthIDSupportTypeTouchID = 1,
/**
* 支持FaceID验证
*/
SDAuthIDSupportTypeFaceID,
/**
* 不支持支持验证
*/
SDAuthIDSupportTypeNone,
};
/**
* TouchID/FaceID 状态
*/
typedef NS_ENUM(NSUInteger, SDAuthIDState){
/**
* 当前设备不支持TouchID/FaceID
*/
SDAuthIDStateNotSupport = 0,
/**
* TouchID/FaceID 验证成功
*/
SDAuthIDStateSuccess = 1,
/**
* TouchID/FaceID 验证失败
*/
SDAuthIDStateFail = 2,
/**
* TouchID/FaceID 被用户手动取消
*/
SDAuthIDStateUserCancel = 3,
/**
* 用户不使用TouchID/FaceID,选择手动输入密码
*/
SDAuthIDStateInputPassword = 4,
/**
* TouchID/FaceID 被系统取消 (如遇到来电,锁屏,按了Home键等)
*/
SDAuthIDStateSystemCancel = 5,
/**
* TouchID/FaceID 无法启动,因为用户没有设置密码
*/
SDAuthIDStatePasswordNotSet = 6,
/**
* TouchID/FaceID 无法启动,因为用户没有设置TouchID/FaceID
*/
SDAuthIDStateTouchIDNotSet = 7,
/**
* TouchID/FaceID 无效
*/
SDAuthIDStateTouchIDNotAvailable = 8,
/**
* TouchID/FaceID 被锁定(连续多次验证TouchID/FaceID失败,系统需要用户手动输入密码)
*/
SDAuthIDStateTouchIDLockout = 9,
/**
* 当前软件被挂起并取消了授权 (如App进入了后台等)
*/
SDAuthIDStateAppCancel = 10,
/**
* 当前软件被挂起并取消了授权 (LAContext对象无效)
*/
SDAuthIDStateInvalidContext = 11,
/**
* 系统版本不支持TouchID/FaceID (必须高于iOS 8.0才能使用)
*/
SDAuthIDStateVersionNotSupport = 12
};
typedef void (^SDAuthIDStateBlock)(SDAuthIDState state, NSError *error);
SDAuthID : NSObject
+ (instancetype)sharedInstance;
/**
判断设备支持哪种认证方式 TouchID & FaceID
*/
- (SDAuthIDSupportType)canSupportBiometrics;
/**
* 启动TouchID/FaceID进行验证
* @param description TouchID/FaceID显示的描述
* @param localizedFallbackTitle 错误描述
* @param block 回调状态的block
*/
- (void)showAuthIDWithDescription:(NSString *)description
localizedFallbackTitle:(NSString *)localizedFallbackTitle
block:(SDAuthIDStateBlock)block;
/**
* 启动TouchID
* @param description TouchID/FaceID显示的描述
* @param localizedFallbackTitle 错误描述
* @param block 回调状态的block
*/
- (void)showAuthTouchIDWithDescription:(NSString *)description
localizedFallbackTitle:(NSString *)localizedFallbackTitle
block:(SDAuthIDStateBlock)block;
/**
* 启动TouchID
* @param description TouchID/FaceID显示的描述
* @param localizedFallbackTitle 错误描述
* @param block 回调状态的block
*/
- (void)showAuthFaceIDWithDescription:(NSString *)description
localizedFallbackTitle:(NSString *)localizedFallbackTitle
block:(SDAuthIDStateBlock)block;
实现的代码
SDAuthID.m
#import "SDAuthID.h"
SDAuthID
+ (instancetype)sharedInstance {
static SDAuthID *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[SDAuthID alloc] init];
});
return instance;
}
/**
* 启动TouchID/FaceID进行验证
* @param description TouchID/FaceID显示的描述
* @param localizedFallbackTitle 错误描述
* @param block 回调状态的block
*/
- (void)showAuthIDWithDescription:(NSString *)description
localizedFallbackTitle:(NSString *)localizedFallbackTitle
block:(SDAuthIDStateBlock)block {
if (NSFoundationVersionNumber < NSFoundationVersionNumber_iOS_8_0) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"系统版本不支持TouchID/FaceID (必须高于iOS 8.0才能使用)");
block(SDAuthIDStateVersionNotSupport, nil);
});
return;
}
LAContext *context = [[LAContext alloc] init];
// 认证失败提示信息,为 @"" 则不提示
context.localizedFallbackTitle = localizedFallbackTitle;
NSError *error = nil;
// LAPolicyDeviceOwnerAuthenticationWithBiometrics: 用TouchID/FaceID验证
// LAPolicyDeviceOwnerAuthentication: 用TouchID/FaceID或密码验证, 默认是错误两次或锁定后, 弹出输入密码界面
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&error]) {
[context evaluatePolicy:LAPolicyDeviceOwnerAuthentication localizedReason:description reply:^(BOOL success, NSError * _Nullable error) {
if (success) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"TouchID/FaceID 验证成功");
block(SDAuthIDStateSuccess, error);
});
} else if(error){
if ((iOS 11.0, *)) {
switch (error.code) {
case LAErrorAuthenticationFailed:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"TouchID/FaceID 验证失败");
block(SDAuthIDStateFail, error);
});
break;
}
case LAErrorUserCancel:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"TouchID/FaceID 被用户手动取消");
block(SDAuthIDStateUserCancel, error);
});
}
break;
case LAErrorUserFallback:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"用户不使用TouchID/FaceID,选择手动输入密码");
block(SDAuthIDStateInputPassword, error);
});
}
break;
case LAErrorSystemCancel:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"TouchID/FaceID 被系统取消 (如遇到来电,锁屏,按了Home键等)");
block(SDAuthIDStateSystemCancel, error);
});
}
break;
case LAErrorPasscodeNotSet:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"TouchID/FaceID 无法启动,因为用户没有设置密码");
block(SDAuthIDStatePasswordNotSet, error);
});
}
break;
case LAErrorBiometryNotEnrolled:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"TouchID/FaceID 无法启动,因为用户没有设置TouchID/FaceID");
block(SDAuthIDStateTouchIDNotSet, error);
});
}
break;
case LAErrorBiometryNotAvailable:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"TouchID/FaceID 无效");
block(SDAuthIDStateTouchIDNotAvailable, error);
});
}
break;
case LAErrorBiometryLockout:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"TouchID/FaceID 被锁定(连续多次验证TouchID/FaceID失败,系统需要用户手动输入密码)");
block(SDAuthIDStateTouchIDLockout, error);
});
}
break;
case LAErrorAppCancel:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"当前软件被挂起并取消了授权 (如App进入了后台等)");
block(SDAuthIDStateAppCancel, error);
});
}
break;
case LAErrorInvalidContext:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"当前软件被挂起并取消了授权 (LAContext对象无效)");
block(SDAuthIDStateInvalidContext, error);
});
}
break;
default:
break;
}
} else {
// iOS 11.0以下的版本只有 TouchID 认证
switch (error.code) {
case LAErrorAuthenticationFailed:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"TouchID 验证失败");
block(SDAuthIDStateFail, error);
});
break;
}
case LAErrorUserCancel:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"TouchID 被用户手动取消");
block(SDAuthIDStateUserCancel, error);
});
}
break;
case LAErrorUserFallback:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"用户不使用TouchID,选择手动输入密码");
block(SDAuthIDStateInputPassword, error);
});
}
break;
case LAErrorSystemCancel:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"TouchID 被系统取消 (如遇到来电,锁屏,按了Home键等)");
block(SDAuthIDStateSystemCancel, error);
});
}
break;
case LAErrorPasscodeNotSet:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"TouchID 无法启动,因为用户没有设置密码");
block(SDAuthIDStatePasswordNotSet, error);
});
}
break;
case LAErrorTouchIDNotEnrolled:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"TouchID 无法启动,因为用户没有设置TouchID");
block(SDAuthIDStateTouchIDNotSet, error);
});
}
break;
//case :{
case LAErrorTouchIDNotAvailable:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"TouchID 无效");
block(SDAuthIDStateTouchIDNotAvailable, error);
});
}
break;
case LAErrorTouchIDLockout:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"TouchID 被锁定(连续多次验证TouchID失败,系统需要用户手动输入密码)");
block(SDAuthIDStateTouchIDLockout, error);
});
}
break;
case LAErrorAppCancel:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"当前软件被挂起并取消了授权 (如App进入了后台等)");
block(SDAuthIDStateAppCancel, error);
});
}
break;
case LAErrorInvalidContext:{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"当前软件被挂起并取消了授权 (LAContext对象无效)");
block(SDAuthIDStateInvalidContext, error);
});
}
break;
default:
break;
}
}
}
}];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"当前设备不支持TouchID/FaceID");
block(SDAuthIDStateNotSupport, error);
});
}
}
/**
* 启动TouchID
* @param description TouchID/FaceID显示的描述
* @param localizedFallbackTitle 错误描述
* @param block 回调状态的block
*/
- (void)showAuthTouchIDWithDescription:(NSString *)description
localizedFallbackTitle:(NSString *)localizedFallbackTitle
block:(SDAuthIDStateBlock)block {
[self showAuthIDWithDescription:description localizedFallbackTitle:localizedFallbackTitle block:block];
}
/**
* 启动TouchID
* @param description TouchID/FaceID显示的描述
* @param localizedFallbackTitle 错误描述
* @param block 回调状态的block
*/
- (void)showAuthFaceIDWithDescription:(NSString *)description
localizedFallbackTitle:(NSString *)localizedFallbackTitle
block:(SDAuthIDStateBlock)block {
[self showAuthIDWithDescription:description localizedFallbackTitle:localizedFallbackTitle block:block];
}
/**
判断设备支持哪种认证方式 TouchID & FaceID
*/
- (SDAuthIDSupportType)canSupportBiometrics {
LAContext *context = [[LAContext alloc] init];
NSError *error;
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
if (error != nil) {
return SDAuthIDSupportTypeNone;
}
if ((iOS 11.0, *)) {
return context.biometryType == LABiometryTypeTouchID ? SDAuthIDSupportTypeTouchID:SDAuthIDSupportTypeFaceID ;
}
}
return SDAuthIDSupportTypeNone;
}
具体使用方式如下;
需要在plist文件中添加 Privacy-Face ID Usage Description 属性
<key>NSFaceIDUsageDescription</key>
<string>允许设备访问Face ID</string>
使用方式
判断支持的类型
SDAuthIDSupportType supportType = [[SDAuthID sharedInstance] canSupportBiometrics];
SDAuthIDSupportType supportType = [[SDAuthID sharedInstance] canSupportBiometrics];
NSString *description = @"";
if(SDAuthIDSupportTypeFaceID == supportType){
description = @"验证已有面容";
} else if(SDAuthIDSupportTypeTouchID == supportType){
description = @"通过Home键验证已有指纹";
} else {
description = @"不支持TouchID/FaceID";
}
[[SDAuthID sharedInstance] showAuthIDWithDescription:description localizedFallbackTitle:@"请输入密码" block:^(SDAuthIDState state, NSError *error) {
if (state == SDAuthIDStateNotSupport) { // 不支持TouchID/FaceID
NSLog(@"对不起,当前设备不支持指纹/面容ID");
} else if(state == SDAuthIDStateFail) { // 认证失败
NSLog(@"指纹/面容ID不正确,认证失败");
} else if(state == SDAuthIDStateTouchIDLockout) { // 多次错误,已被锁定
NSLog(@"多次错误,指纹/面容ID已被锁定,请到手机解锁界面输入密码");
} else if (state == SDAuthIDStateSuccess) { // TouchID/FaceID验证成功
NSLog(@"认证成功!");
}
}];
三、实现flutter 调用指纹、面容ID插件
3.1、创建flutter plugin
创建flutter plugin,我使用的工具是Android studio。
配置如下内容:
- Project name
- Project location
- Description
- Project type: Plugin
- Android language
- iOS language
- Platforms
如图所示
3.2、flutter调用代码实现
flutter 调用原生的代码,需要用到channel,这里使用Method channel
实现调用的platform_interface
import ‘flutter_authid_platform_interface.dart’;
/// An implementation of [FlutterAuthidPlatform] that uses method channels.
class MethodChannelFlutterAuthid extends FlutterAuthidPlatform {
/// The method channel used to interact with the native platform.
final methodChannel = const MethodChannel('flutter_authid');
Future<String?> getPlatformVersion() async {
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
return version;
}
Future<Map<dynamic, dynamic>?> getAuthIDSupportType() async {
final result = await methodChannel.invokeMethod<Map<dynamic, dynamic>>('getAuthIDSupportType');
return result;
}
Future<Map<dynamic, dynamic>?> authVerification({Map<dynamic, dynamic>? arguments}) async {
final result = await methodChannel.invokeMethod<Map<dynamic, dynamic>>('authVerification', arguments);
return result;
}
}
调用的platform_interface的具体的类
import ‘flutter_authid_platform_interface.dart’;
class FlutterAuthid {
Future<String?> getPlatformVersion() {
return FlutterAuthidPlatform.instance.getPlatformVersion();
}
Future<Map<dynamic, dynamic>?> getAuthIDSupportType() {
return FlutterAuthidPlatform.instance.getAuthIDSupportType();
}
///Map<dynamic, dynamic> arguments = {};
// arguments['faceAuthDesc'] = "验证已有面容";
// arguments['touchAuthDesc'] = "通过Home键验证已有指纹";
// arguments['noSupportAuthDesc'] = "不支持TouchID/FaceID";
// arguments['fallbackTitle'] = "请输入密码";
Future<Map<dynamic, dynamic>?> authVerification({Map<dynamic, dynamic>? arguments}) {
return FlutterAuthidPlatform.instance.authVerification(arguments: arguments);
}
}
/**
注意:<key>NSFaceIDUsageDescription</key>
<string>允许设备访问Face ID</string>
*/
3.3、插件的使用实例Example
在flutter插件的example中实现插件的调用,具体代码如下
class AuthPage extends StatefulWidget {
const AuthPage({Key? key}) : super(key: key);
State<AuthPage> createState() => _AuthPageState();
}
class _AuthPageState extends State<AuthPage> {
final _flutterAuthidPlugin = FlutterAuthid();
String authName = "";
String authImage = "";
String hintDescription = "";
String supportType = "";
void initState() {
// TODO: implement initState
super.initState();
setupAuth();
}
Future<void> setupAuth() async {
Map<dynamic, dynamic> result;
// Platform messages may fail, so we use a try/catch PlatformException.
// We also handle the message potentially returning null.
try {
result = await _flutterAuthidPlugin.getAuthIDSupportType() ?? {};
} on PlatformException {
result = {};
}
print("getAuthIDSupportType:$result");
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
String type = result['supportType'].toString();
supportType = type;
if ("2" == type) {
authImage = "auth_face@2x.png";
authName = "面容";
} else if ("1" == type) {
authImage = "auth_finger@2x.png";
authName = "指纹";
}
hintDescription = result['desc'];
});
authVerification();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Auth app'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image(
image: AssetImage("assets/images/$authImage"),
width: 100.0,
height: 100.0,
),
SizedBox(height: 50.0,),
Text(
'SDAuthApp\n $hintDescription以进行登录',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
color: Colors.black,
),
),
SizedBox(height: 50.0,),
TextButton(
onPressed: () {
authVerification();
},
child: Text(
'点击验证$authName',
style: TextStyle(
fontSize: 17,
color: Colors.black,
),
),
)
],
),
),
);
}
// 点击唤起指纹、面容ID验证
Future<void> authVerification() async {
Map<dynamic, dynamic> arguments = {};
arguments['faceAuthDesc'] = "验证已有面容";
arguments['touchAuthDesc'] = "通过Home键验证已有指纹";
arguments['noSupportAuthDesc'] = "不支持TouchID/FaceID";
arguments['fallbackTitle'] = "请输入密码";
Map<dynamic, dynamic> result;
// Platform messages may fail, so we use a try/catch PlatformException.
// We also handle the message potentially returning null.
try {
result =
await _flutterAuthidPlugin.authVerification(arguments: arguments) ??
{};
} on PlatformException {
result = {};
}
print("authVerification:$result");
}
}
四、小结
flutter开发实战-指纹、面容ID验证插件实现,主要iOS开发中的指纹、面容ID验证功能,通过flutter的插件实现调用原生的插件。
学习记录,每天不停进步。