21、指纹认证与网络请求:在登录/注册中使用指纹、指纹Token与Session管理、服务器端验证

好,我们进入第二十一章。这一章讲的是把指纹认证真正用到网络请求里。

说白了,就是怎么用指纹去登录、去注册,而不是每次都输密码。

我在项目里见过太多人把指纹当成「本地解锁」来用,结果上了服务器就懵了。嗯,今天咱们把这个坑填上。

21.1 为什么需要指纹+网络请求?

你想想看,用户打开App,每次都要输密码,烦不烦?

指纹的作用,就是替代「密码输入」这个动作。但服务器不认识你的指纹,它只认Token。

所以我们的核心思路是:用指纹解锁本地存储的密码或Token,然后拿这个去请求服务器

核心原则:指纹只做本地身份验证,不做网络传输。指纹数据永远不出设备。

21.2 登录/注册中使用指纹

先看登录场景。用户第一次登录,肯定要输密码。但第二次呢?

我个人习惯的做法是:

  1. 用户首次登录成功,服务器返回一个长期有效的refresh_token
  2. 把这个refresh_token加密存到本地,用指纹保护
  3. 下次启动时,弹出指纹验证,验证通过后解密refresh_token
  4. 用refresh_token去换新的access_token

注册流程其实类似。注册成功后,直接提示用户「是否开启指纹登录」。

小技巧:我建议在注册成功页加一个开关,默认开启指纹。用户接受度会高很多。

21.3 指纹Token与Session管理

这里有个关键点:指纹验证成功后,我们拿到的不是「指纹Token」,而是「用指纹解锁出来的Token」。

我画了一张图,帮你理清关系:

用户指纹 本地指纹验证 BiometricPrompt 解密本地Token EncryptedSharedPreferences 网络请求(带Token) 服务器 指纹失败 → 走密码登录 图:指纹认证与网络请求的完整流程 指纹只做本地解锁,Token走网络传输

看到没?指纹验证只是「开锁」的动作,真正的身份凭证是Token。

21.4 代码实现:指纹登录流程

直接上代码。这是我常用的一个工具类核心部分:

// 1. 指纹验证成功后,解密Token
private void onFingerprintSuccess() {
    try {
        // 从EncryptedSharedPreferences读取加密的refresh_token
        String encryptedToken = prefs.getString("refresh_token", "");
        if (TextUtils.isEmpty(encryptedToken)) {
            // 没有存过Token,走密码登录
            showPasswordLogin();
            return;
        }
        
        // 解密(这里用了Android Keystore + AES)
        String refreshToken = CryptoUtils.decrypt(encryptedToken);
        
        // 2. 用refresh_token换access_token
        ApiService api = RetrofitClient.getInstance().create(ApiService.class);
        Call<TokenResponse> call = api.refreshToken(refreshToken);
        call.enqueue(new Callback<TokenResponse>() {
            @Override
            public void onResponse(Call<TokenResponse> call, Response<TokenResponse> response) {
                if (response.isSuccessful()) {
                    // 3. 保存新的Token,继续正常请求
                    saveTokens(response.body());
                    navigateToMain();
                } else {
                    // refresh_token过期,需要重新登录
                    showPasswordLogin();
                }
            }
            
            @Override
            public void onFailure(Call<TokenResponse> call, Throwable t) {
                // 网络错误,提示用户
                showError("网络异常,请稍后重试");
            }
        });
    } catch (Exception e) {
        // 解密失败,可能是数据损坏
        showPasswordLogin();
    }
}

注意:千万不要把指纹特征值或者指纹模板传到服务器。这是违反Android安全规范的,也是巨大的安全隐患。

21.5 服务器端验证

服务器端其实不需要知道客户端用了指纹。它只认Token。

但有一个细节:服务器应该区分「密码登录」和「指纹登录」吗?

我个人建议:不需要区分。服务器统一验证Token有效性即可。

不过,如果你要做安全审计,可以在Token的payload里加一个字段:

{
  "user_id": 12345,
  "login_method": "fingerprint",  // 或 "password"
  "iat": 1700000000,
  "exp": 1700086400
}

这样服务器可以记录登录方式,但验证逻辑完全一样。

21.6 避坑指南

我曾经在一个项目里踩过一个坑:用户指纹验证成功后,我直接拿本地存的密码去请求登录接口。

结果呢?密码在本地存的是明文,虽然用了指纹保护,但一旦设备被root,密码就裸奔了。

后来我改成存refresh_token,而且用EncryptedSharedPreferences加密存储。这才算真正安全。

还有几个坑,我列出来:

  • Token过期处理:指纹登录成功后,如果refresh_token也过期了,必须让用户重新输密码。不要无限重试。
  • 设备变更:用户删除了指纹,或者新增了指纹,之前存的Token应该失效。建议监听KeyguardManager的指纹变化。
  • 多账号场景:如果App支持多账号,每个账号的Token要分开存,指纹验证后要选择对应的账号。

21.7 总结

指纹认证与网络请求结合,核心就一句话:指纹做本地锁,Token做网络证

你想想看,这样做的好处是什么?

  • 用户方便:不用记密码,按一下指纹就行
  • 安全:指纹数据不出设备,Token加密存储
  • 服务器无感:后端不需要任何改动

嗯,这一章的内容就这些。下一章我们会聊更进阶的话题,但今天先把基础打牢。

课后思考:如果用户手机丢失,别人用他的指纹能登录你的App吗?你的Token存储方案能扛住吗?


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