翻译自:苹果官方文档
https://developer.apple.com/documentation/localauthentication/logging_a_user_into_your_app_with_face_id_or_touch_id?language=objc
使用Face ID或Touch ID将用户登录到您的应用程序
通过生物身份验证来补充您自己的身份验证方案,使用户可以轻松访问应用程序的敏感部分。
总览
用户喜欢Touch ID和Face ID,因为这些身份验证机制使他们能够以最小的努力安全地访问其设备。当您采用LocalAuthentication
框架时,可以简化典型情况下的用户身份验证体验,同时为无法使用生物识别功能的情况提供后备选项。
设置面部识别码用法说明
在任何使用生物识别技术的项目中,请将NSFaceIDUsageDescription
键包含在应用程序的Info.plist
文件中。没有此密钥,系统将不允许您的应用使用Face ID
。该密钥的值是系统在您的应用首次尝试使用Face ID
时向用户显示的字符串。该字符串应清楚地说明您的应用为何需要访问此身份验证机制。系统不需要Touch ID
的类似用法说明。
创建和配置上下文
您可以使用LAContext实例在应用程序中执行生物特征认证,该实例代理您的应用程序与Secure Enclave之间的交互。首先创建一个上下文:
var context = LAContext()
您可以定制上下文使用的消息传递,以指导用户完成流程。例如,您可以为出现在各种警报视图中的“取消”按钮设置自定义消息:
context.localizedCancelTitle = "Enter Username/Password"
这有助于用户了解,当他们点击按钮时,他们将恢复到您的常规身份验证过程。
测试策略可用性
在尝试进行身份验证之前,请通过调用canEvaluatePolicy(_:error :)
方法进行测试,以确保您确实具有身份验证的能力:
if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
从LAPolicy
枚举中选择一个要测试的值。该策略控制身份验证的行为。例如,此样本中使用的deviceOwnerAuthentication
策略指示当生物识别失败或不可用时,允许还原为密码。另外,您可以指定deviceOwnerAuthenticationWithBiometrics
政策,该政策不允许还原为设备密码。
评估政策
准备好进行身份验证时,请使用已经测试过的相同策略来调用validatePolicy(_:localizedReason:reply :)
方法:
context.evaluatePolicy(.deviceOwnerAuthentication,localizedReason:reason){成功,错误
if success {
// Move to the main thread because a state update triggers UI changes.
DispatchQueue.main.async { [unowned self] in
self.state = .loggedin
}
} else {
print(error?.localizedDescription ?? "Failed to authenticate")
// Fall back to a asking for username and password.
// ...
}
}
对于Touch ID
,或者当用户输入密码时,系统会显示验证您在方法调用中提供的原因。重要的是要提供清晰的说明(针对您经营的任何地区),以解释您的应用为何要求用户进行身份验证的原因。您的应用名称已经出现在您提供名称的前面,因此您无需在消息中包含该名称。
(可选)调整您的用户界面以容纳面部ID
除了生物识别扫描操作本身之外,面部ID与触摸ID相比还具有重要的区别。当您的应用尝试使用Touch ID时,系统会显示一条消息,要求用户出示手指进行扫描。用户有时间考虑并可能通过取消提示来中止操作。当您的应用调用人脸ID时,设备会立即开始扫描用户的脸部。用户没有最终的取消机会。为了适应这种行为差异,您可能需要提供不同的UI,具体取决于设备上可用的生物识别技术的种类。
该示例应用程序通过包含带有消息的文本标签来提供不同的UI,该消息警告Face ID用户点击该按钮会立即进行Face ID扫描。该应用程序默认情况下隐藏标签,仅针对当前已注销的具有Face ID的用户显示。具有Touch ID(或完全没有生物特征)的用户不需要此消息,因为他们可以在实际进行扫描之前取消操作。
您可以通过读取上下文的biometryType
参数来测试设备支持哪种生物识别:
faceIDLabel.isHidden = (state == .loggedin) || (context.biometryType != .faceID)
在上下文上至少运行一次canEvaluatePolicy(_:error :)
方法之后,此参数仅包含有意义的值。
提供生物识别技术的后备替代方案
由于各种原因,身份验证有时会失败或不可用:
用户的设备没有Touch ID或Face ID。
该用户尚未注册生物特征识别,或者未设置密码。
用户取消操作。
触摸ID或面部ID无法识别用户。
您之前已通过调用invalidate()
方法使上下文无效。
有关可能的错误情况的完整列表,请参见LAError.Code
。
此示例应用未实现替代身份验证。在真实应用中,如果遇到本地身份验证错误,请退回自己的身份验证方案,例如询问用户名和密码。使用生物识别技术来补充您已经在做的事情。请勿将生物识别技术作为唯一的身份验证选项。
Swift版本:
import UIKit
import LocalAuthentication
class ViewController: UIViewController {
@IBOutlet weak var loginButton: UIButton!
@IBOutlet weak var stateView: UIView!
@IBOutlet weak var faceIDLabel: UILabel!
/// An authentication context stored at class scope so it's available for use during UI updates.
var context = LAContext()
/// The available states of being logged in or not.
enum AuthenticationState {
case loggedin, loggedout
}
/// The current authentication state.
var state = AuthenticationState.loggedout {
// Update the UI on a change.
didSet {
loginButton.isHighlighted = state == .loggedin // The button text changes on highlight.
stateView.backgroundColor = state == .loggedin ? .green : .red
// FaceID runs right away on evaluation, so you might want to warn the user.
// In this app, show a special Face ID prompt if the user is logged out, but
// only if the device supports that kind of authentication.
faceIDLabel.isHidden = (state == .loggedin) || (context.biometryType != .faceID)
}
}
override func viewDidLoad() {
super.viewDidLoad()
// The biometryType, which affects this app's UI when state changes, is only meaningful
// after running canEvaluatePolicy. But make sure not to run this test from inside a
// policy evaluation callback (for example, don't put next line in the state's didSet
// method, which is triggered as a result of the state change made in the callback),
// because that might result in deadlock.
context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil)
// Set the initial app state. This impacts the initial state of the UI as well.
state = .loggedout
}
/// Logs out or attempts to log in when the user taps the button.
@IBAction func tapButton(_ sender: UIButton) {
if state == .loggedin {
// Log out immediately.
state = .loggedout
} else {
// Get a fresh context for each login. If you use the same context on multiple attempts
// (by commenting out the next line), then a previously successful authentication
// causes the next policy evaluation to succeed without testing biometry again.
// That's usually not what you want.
context = LAContext()
context.localizedCancelTitle = "Enter Username/Password"
// First check if we have the needed hardware support.
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
let reason = "Log in to your account"
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason ) { success, error in
if success {
// Move to the main thread because a state update triggers UI changes.
DispatchQueue.main.async { [unowned self] in
self.state = .loggedin
}
} else {
print(error?.localizedDescription ?? "Failed to authenticate")
// Fall back to a asking for username and password.
// ...
}
}
} else {
print(error?.localizedDescription ?? "Can't evaluate policy")
// Fall back to a asking for username and password.
// ...
}
}
}
}
OC版本:
#import "ViewController.h"
#import
@interface ViewController () {
BOOL _isAuthrized;
}
@property(nonatomic, strong) LAContext *context;
@property(nonatomic, strong) UIButton *loginButton;
@property(nonatomic, strong) UIView *stateView;
@property(nonatomic, strong) UILabel *faceIdL;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setUI];
_isAuthrized = NO;
NSError *error = nil;
BOOL ss = [self.context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&error];
}
- (UIView *)stateView {
if (nil == _stateView) {
_stateView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
_stateView.backgroundColor = UIColor.greenColor;
}
return _stateView;
}
- (UIButton *)loginButton {
if (nil == _loginButton) {
_loginButton = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
[_loginButton setTitle:@"log in" forState:UIControlStateNormal];
[_loginButton addTarget:self action:@selector(tapBtn) forControlEvents:UIControlEventTouchUpInside];
}
return _loginButton;
}
- (void)setUI {
[self.view addSubview:self.stateView];
[self.view addSubview:self.loginButton];
}
#pragma mark -
- (void)tapBtn {
if (_isAuthrized == YES) {
_isAuthrized = NO;
[self changeUI];
} else {
self.context = LAContext.new;
self.context.localizedCancelTitle = @"Enter Username/Password";
NSError *error = nil;
if ([self.context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&error]) {
[self.context evaluatePolicy:LAPolicyDeviceOwnerAuthentication localizedReason:@"log in to your account" reply:^(BOOL success, NSError * _Nullable error) {
if (success) {
dispatch_async(dispatch_get_main_queue(), ^{
self->_isAuthrized = YES;
});
NSLog(@"%@",@"验证成功");
[self changeUI];
} else {
NSLog(@"%@",@"验证失败");
self->_isAuthrized = NO;
[self changeUI];
}
}];
} else {
NSLog(@"%@",@"执行失败");
self->_isAuthrized = NO;
}
}
[self changeUI];
}
- (void)changeUI {
NSLog(@"%d",_isAuthrized);
dispatch_async(dispatch_get_main_queue(), ^{
[self.loginButton setTitle:self->_isAuthrized ? @"log out" : @"log in" forState:UIControlStateNormal];
self.stateView.backgroundColor = self->_isAuthrized ? UIColor.greenColor : UIColor.redColor;
});
}
@end