29、人脸识别实战项目:登录模块集成人脸识别、支付确认人脸识别、应用锁人脸识别
好,终于到了实战环节。前面我们聊了那么多理论、API、安全策略,说白了都是为了这一刻。今天咱们就把人脸识别真正塞进三个最常见的场景里:登录、支付确认、应用锁。
这三个场景,我当年在做一个金融类App时全踩过坑。登录还好说,支付确认那块差点被产品经理逼疯——又要快又要安全,你想想看,这本身就是矛盾的。但后来摸索出一套方案,今天全部分享给你。
一、整体架构设计
先别急着写代码。我习惯先画一张图,把整个流程理清楚。人脸识别集成不是简单调个API就完事,它涉及业务逻辑、安全校验、UI交互等多个层面。
这张图你看懂了吗?三个业务场景共用底层的 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