iOS 生物识别身份验证:仅在第二次生物识别失败尝试后尝试使用密码

iOS Biometric authentication: Try Password work only after the second biometric fail attempt

我第一次在 iOS 上尝试生物认证。

我的 touch id 验证码运行良好。但是如果 touch id 失败,我想使用设备 PIN 进行身份验证。但这仅在第二次触摸 ID 尝试失败后才有效。第一次失败时,会显示带有 'Try Password' 按钮的警报。但是当触摸它时,它没有去屏幕输入设备密码,而是再次显示输入触摸 ID 警报。

现在如果 touch id 再次失败并且我触摸输入密码按钮。它将进入输入设备 PIN 的屏幕。

但是为什么第一次不行呢?来自 Apple 文档:

The fallback button is initially hidden. For Face ID, after the first unsuccessful authentication attempt, the user will be prompted to try Face ID again or cancel. The fallback button is displayed after the second unsuccessful Face ID attempt. For Touch ID, the fallback button is displayed after the first unsuccessful Touch ID attempt.

我看到它与 google 支付等应用程序一起使用。我在这里做错了什么。

这是我的代码。

public partial class AuthenticationViewController : UIViewController
{

    private LAContext context;

    public AuthenticationViewController(IntPtr handle) : base(handle)
    {
    }

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        if (UserDefaultsManager.RememberMe)
            TryAuthenticate();
        else
            AppDelegate.Instance.GotoLoginController();
    }

    private void TryAuthenticate()
    {
        context = new LAContext();
        NSError error = null;

        if (UIDevice.CurrentDevice.CheckSystemVersion(11, 0) &&
            context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out error)) {
            // Biometry is available on the device
            context.EvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics,
                "Unlock SmartFHR", HandleLAContextReplyHandler);
        } else {
            // Biometry is not available on the device
            if (error != null) {
                HandleLAContextReplyHandler(false, error);
            } else {
                TryDevicePinAuthentication(error);
            }
        }
    }

    private void TryDevicePinAuthentication(NSError error)
    {
        if (context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthentication, out error)) {
            context.EvaluatePolicy(LAPolicy.DeviceOwnerAuthentication,
                "Unlock SmartFHR", HandleLAContextReplyHandler);
        }
    }

    private void HandleLAContextReplyHandler(bool success, NSError error)
    {
        DispatchQueue.MainQueue.DispatchAsync(() => {
            if (success) {
                ContinueAfterAuthSuccess();
                return;
            }
            switch (error.Code) {
                case (long)LAStatus.UserCancel:
                    AppDelegate.Instance.GotoLoginController(true);
                    break;
                case (long)LAStatus.UserFallback:
                case (long)LAStatus.BiometryNotEnrolled:
                case (long)LAStatus.BiometryNotAvailable:
                    TryDevicePinAuthentication(error);
                    break;
            }
        });
    }

    private void ContinueAfterAuthSuccess()
    {
        if (Storyboard.InstantiateViewController("SplashController") is SplashController vc)
            AppDelegate.Instance.Window.RootViewController = vc;
    }

}

当第一次触摸 ID 尝试失败并且我触摸“尝试密码”按钮时,我看到它调用 HandleLAContextReplyHandler,错误代码为 LAStatus.UserFallback。

LAPolicyDeviceOwnerAuthentication 的文档说:

If Touch ID or Face ID is available, enrolled, and not disabled, the user is asked for that first.

因此,当您使用 TryDevicePinAuthentication 显示身份验证 window 时,它仍将首先显示生物特征 window。

如果想让用户输入密码进行认证,我觉得DeviceOwnerAuthentication就够了。

private void TryAuthenticate()
{
    context = new LAContext();

    // if Biometry is available on the device, it will show it first
    context.EvaluatePolicy(LAPolicy.DeviceOwnerAuthentication, "Unlock SmartFHR", HandleLAContextReplyHandler);
}

这样只需要处理用户取消本次认证的情况即可。因为当用户点击回退按钮时,它会自动弹出输入 window 的密码:

private void HandleLAContextReplyHandler(bool success, NSError error)
{
    DispatchQueue.MainQueue.DispatchAsync(() => {
        if (success)
        {
            ContinueAfterAuthSuccess();
            return;
        }
        switch (error.Code)
        {
            case (long)LAStatus.UserCancel:
                AppDelegate.Instance.GotoLoginController(true);
                break;
            default:
                break;
        }
    });
}