29、人脸识别实战项目:登录模块集成人脸识别、支付确认人脸识别、应用锁人脸识别

好,终于到了实战环节。前面我们聊了那么多理论、API、安全策略,说白了都是为了这一刻。今天咱们就把人脸识别真正塞进三个最常见的场景里:登录、支付确认、应用锁。

这三个场景,我当年在做一个金融类App时全踩过坑。登录还好说,支付确认那块差点被产品经理逼疯——又要快又要安全,你想想看,这本身就是矛盾的。但后来摸索出一套方案,今天全部分享给你。

一、整体架构设计

先别急着写代码。我习惯先画一张图,把整个流程理清楚。人脸识别集成不是简单调个API就完事,它涉及业务逻辑、安全校验、UI交互等多个层面。

人脸识别集成架构图 登录模块 支付确认 应用锁 BiometricManager 统一认证服务 authenticate() → 回调 → 结果处理 CryptoObject 加密 密钥生成与校验 SharedPreferences 存储 加密存储用户偏好 安全策略校验 设备安全等级检查

这张图你看懂了吗?三个业务场景共用底层的 BiometricManager 服务。说白了,核心逻辑是一样的,只是业务回调不同。

二、登录模块集成人脸识别

登录场景是最简单的。用户输入账号密码后,可以选择开启人脸识别。下次登录时直接刷脸就行。

核心思路:登录成功后保存一个 token,同时记录用户开启了人脸登录。下次启动 App 时,先检查是否有 token 和人脸登录开关,有的话直接调起人脸识别。

2.1 登录成功后保存配置

// 登录成功后调用
private void onLoginSuccess(String token) {
    // 保存 token
    SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
    prefs.edit().putString("login_token", token).apply();

    // 询问用户是否开启人脸登录
    if (BiometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
        showEnableFaceDialog();
    }
}

嗯,这里要注意一点。我当年犯过一个错——直接保存了明文 token。后来被安全审计骂了一顿。正确的做法是用 EncryptedSharedPreferences 来存。

2.2 人脸登录流程

private void startFaceLogin() {
    BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
            .setTitle("人脸登录")
            .setSubtitle("验证身份以登录")
            .setNegativeButtonText("取消")
            .build();

    BiometricPrompt biometricPrompt = new BiometricPrompt(this, 
        new BiometricPrompt.AuthenticationCallback() {
            @Override
            public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
                // 验证成功,用保存的 token 自动登录
                String token = prefs.getString("login_token", "");
                autoLoginWithToken(token);
            }

            @Override
            public void onAuthenticationFailed() {
                // 人脸识别失败,提示用户重试或使用密码
                showRetryDialog();
            }
        });

    biometricPrompt.authenticate(promptInfo);
}

避坑指南:我曾经遇到过用户换了手机后,人脸识别一直失败。原因是旧手机的 token 没有做设备绑定。建议在保存 token 时同时保存设备 ID,每次人脸登录前校验设备是否一致。

三、支付确认人脸识别

支付确认是三个场景里最敏感的。为什么?因为涉及真金白银。我当年做支付模块时,产品经理要求:人脸识别失败后必须回退到密码输入,不能直接让交易失败。

3.1 支付确认的流程

支付确认和登录最大的区别在于:登录只需要验证「你是不是你」,但支付确认需要验证「你是不是你」并且「你确实想付这笔钱」。所以 UI 上要展示支付金额和收款方。

private void startPaymentConfirm(double amount, String payee) {
    BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
            .setTitle("支付确认")
            .setSubtitle("支付金额: ¥" + amount)
            .setDescription("收款方: " + payee)
            .setNegativeButtonText("使用密码支付")
            .build();

    BiometricPrompt biometricPrompt = new BiometricPrompt(this, 
        new BiometricPrompt.AuthenticationCallback() {
            @Override
            public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
                // 人脸验证通过,执行支付
                executePayment(amount, payee);
            }

            @Override
            public void onAuthenticationError(int errorCode, CharSequence errString) {
                // 用户点击了"使用密码支付"
                if (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) {
                    showPasswordInputDialog();
                }
            }
        });

    biometricPrompt.authenticate(promptInfo);
}

重要提醒:支付确认场景下,千万不要在 onAuthenticationSucceeded 里直接执行支付。正确的做法是:人脸验证通过后,生成一个一次性签名,后端验证签名后再执行扣款。这样可以防止重放攻击。

3.2 使用 CryptoObject 增强安全性

支付场景我强烈建议加上 CryptoObject。说白了,就是把人脸验证结果和加密密钥绑定在一起,防止中间人篡改。

// 生成密钥
KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder("payment_key",
        KeyProperties.PURPOSE_SIGN)
        .setUserAuthenticationRequired(true)
        .setInvalidatedByBiometricEnrollment(true)
        .build();

KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyGenerator keyGenerator = KeyGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
keyGenerator.init(keySpec);
keyGenerator.generateKey();

// 使用 CryptoObject 进行人脸验证
CryptoObject cryptoObject = new CryptoObject(
        keyStore.getKey("payment_key", null));
biometricPrompt.authenticate(promptInfo, cryptoObject);

你想想看,加了 CryptoObject 之后,就算有人拿到了你的人脸数据,没有对应的密钥也白搭。这就是双重保险。

四、应用锁人脸识别

应用锁这个场景,说白了就是给 App 加一把「人脸锁」。用户每次打开 App 或者切换到前台时,都需要刷脸验证。

4.1 应用锁的实现思路

应用锁和登录、支付最大的不同在于:它需要监听应用的前后台切换。我习惯用 Application.ActivityLifecycleCallbacks 来实现。

public class AppLockManager implements Application.ActivityLifecycleCallbacks {
    private int activityCount = 0;
    private boolean isLocked = false;

    @Override
    public void onActivityStarted(Activity activity) {
        activityCount++;
        // 从后台切换到前台
        if (activityCount == 1 && isLocked) {
            showFaceLock(activity);
        }
    }

    @Override
    public void onActivityStopped(Activity activity) {
        activityCount--;
        // 进入后台,锁定
        if (activityCount == 0) {
            isLocked = true;
        }
    }

    private void showFaceLock(Activity activity) {
        BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
                .setTitle("应用已锁定")
                .setSubtitle("请验证身份以解锁")
                .setNegativeButtonText("退出应用")
                .build();

        BiometricPrompt biometricPrompt = new BiometricPrompt(activity,
            new BiometricPrompt.AuthenticationCallback() {
                @Override
                public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
                    isLocked = false;
                }

                @Override
                public void onAuthenticationError(int errorCode, CharSequence errString) {
                    if (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) {
                        activity.finishAffinity(); // 退出应用
                    }
                }
            });

        biometricPrompt.authenticate(promptInfo);
    }
}

个人经验:应用锁这里有个坑——如果用户正在使用 App 时突然来电话,Activity 会走 onStop,然后电话挂断后又走 onStart。这时候如果每次都弹人脸锁,用户体验极差。我的解决方案是:加一个 30 秒的缓冲时间,只有离开超过 30 秒才重新锁定。

五、三个场景的对比总结

场景 安全等级 是否需要 CryptoObject 失败回退策略 特殊注意点
登录 可选 密码登录 token 设备绑定
支付确认 必须 密码支付 一次性签名防重放
应用锁 可选 退出应用 前后台切换缓冲

看到这个表格你应该明白了。三个场景虽然都调用了 BiometricPrompt,但安全策略和业务逻辑完全不同。说白了,人脸识别只是「验证身份」这一步,真正的业务安全要靠你自己设计。

最后说一句:人脸识别不是银弹。我见过太多团队把人脸识别当成万能钥匙,结果上线后被各种绕过。记住:人脸识别只是验证手段之一,永远不要完全依赖单一验证方式。多因子认证才是王道。


公众号:蓝海资料掘金营,微信deep3321