认证(Authentication)是Android应用安全体系的第一道门槛,无论是登录态校验、支付确认还是敏感数据访问,都离不开可靠的认证机制。针对Android Authentication的测试,核心目标是验证认证流程的正确性、安全性和异常容错能力。本文将从认证方案原理、测试环境搭建、具体测试用例设计三个层面展开,配合可运行的代码示例,给出一套可以直接落地的测试方案。

一、Android常见认证方案及其测试重点
Android平台上的认证方式大致分为三类:第一类是传统的账号密码与令牌(Token)方案,客户端把凭证提交给服务端,服务端返回Session或JWT;第二类是设备级认证,利用Android Keystore系统生成密钥,密钥只有在用户通过锁屏认证后才能使用;第三类是生物识别认证,通过BiometricPrompt调用指纹、人脸或虹膜进行身份确认。
不同方案的测试重点差异很大。账号密码方案要重点关注传输加密、Token过期刷新、本地存储是否明文;Keystore方案要验证密钥的setUserAuthenticationRequired(true)配置是否生效,即未解锁状态下调用密钥应当抛出UserNotAuthenticatedException;生物识别方案则要覆盖认证成功、认证失败、取消、锁定等多条分支。
一个常见的误区是认为认证测试只验证"登录成功"这一条正向路径。实际上,攻击面恰恰集中在反向路径上:伪造Token、绕过生物识别、密钥在后台进程中被非法调用等。测试用例的设计必须覆盖这些场景。
二、搭建认证测试环境与基础代码
认证测试分为两类:单元测试(本地JVM运行)和Instrumented Test(连接真机或模拟器运行)。涉及Keystore和BiometricPrompt的测试必须在真机上执行,因为这两个组件依赖系统服务,本地JVM无法模拟。
先在build.gradle中引入必要的依赖:
dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'androidx.biometric:biometric:1.1.0'
}
接着编写一个使用Keystore的加密工具类,后续的测试都会围绕它展开。这个类的关键点在于初始化密钥时设置了用户认证标志,并且限制了密钥的认证有效期:
public class AuthCryptoManager {
private static final String KEY_NAME = "test_auth_key";
private static final int AUTH_VALIDITY_SECONDS = 30;
private final KeyguardManager keyguardManager;
public AuthCryptoManager(Context context) {
this.keyguardManager =
(KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
}
public SecretKey getOrCreateKey() throws Exception {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
if (keyStore.containsAlias(KEY_NAME)) {
return ((KeyStore.SecretKeyEntry)
keyStore.getEntry(KEY_NAME, null)).getSecretKey();
}
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(AUTH_VALIDITY_SECONDS)
.build();
KeyGenerator generator =
KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
generator.init(spec);
return generator.generateKey();
}
public boolean isDeviceSecure() {
return keyguardManager.isDeviceSecure();
}
public boolean hasAuthenticatedRecently() {
return keyguardManager.isUserAuthenticationUnlocked();
}
}
上面代码中setUserAuthenticationValidityDurationSeconds(30)表示密钥在用户通过锁屏认证后的30秒内可用,超过这个窗口再次使用密钥就会触发认证要求。这个时间是测试超时场景的关键参数。
三、编写Instrumented认证测试用例
Instrumented Test运行在设备上,可以直接调用系统API。下面这个测试类覆盖了设备安全状态检查和密钥认证要求两个基础场景:
@RunWith(AndroidJUnit4.class)
public class AuthCryptoManagerTest {
private AuthCryptoManager cryptoManager;
@Before
public void setUp() {
Context context = ApplicationProvider.getApplicationContext();
cryptoManager = new AuthCryptoManager(context);
}
@Test
public void deviceWithScreenLock_reportsSecure() {
// 前提:测试设备已设置锁屏密码或图案
assertTrue("设备必须已设置锁屏才能测试认证流程",
cryptoManager.isDeviceSecure());
}
@Test
public void keyOperation_afterAuthTimeout_requiresReauth() throws Exception {
SecretKey key = cryptoManager.getOrCreateKey();
assertNotNull("密钥应创建成功", key);
// 用户刚解锁屏幕后,认证窗口内可直接加密
if (cryptoManager.hasAuthenticatedRecently()) {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] result = cipher.doFinal("敏感数据".getBytes());
assertNotNull(result);
} else {
// 认证已过期,初始化密码器应抛出UserNotAuthenticatedException
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
fail("认证过期时不应允许直接使用密钥");
} catch (UserNotAuthenticatedException expected) {
// 符合预期的行为
}
}
}
}
这段测试中有一个技巧:利用hasAuthenticatedRecently()判断当前是否处于认证有效窗口,从而在同一个用例中动态选择断言分支。也可以更严格地把两个分支拆成两个用例,通过adb命令控制锁屏状态后再分别运行。
四、BiometricPrompt生物识别的UI级测试
BiometricPrompt的测试难点在于系统弹窗由Framework绘制,Espresso默认无法操作它。常用的做法有两种:一是使用UiAutomator直接对系统弹窗做点击;二是利用BiometricPrompt提供的回调接口做接口级测试。下面展示接口级测试的写法:
@RunWith(AndroidJUnit4.class)
public class BiometricAuthTest {
@Test
public void biometricPrompt_successCallback_releasesKey() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
Executor executor = ContextCompat.getMainExecutor(context);
BiometricPrompt prompt = new BiometricPrompt(
(FragmentActivity) currentActivity(), executor,
new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationSucceeded(
BiometricPrompt.AuthenticationResult result) {
// 认证成功,从结果中取出已授权的Cipher
Cipher cipher = result.getCryptoObject().getCipher();
assertNotNull("认证成功后应拿到可用Cipher", cipher);
latch.countDown();
}
@Override
public void onAuthenticationError(int code, CharSequence msg) {
errorRef.set(code);
latch.countDown();
}
});
AuthCryptoManager manager = new AuthCryptoManager(context);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, manager.getOrCreateKey());
BiometricPrompt.CryptoObject cryptoObject =
new BiometricPrompt.CryptoObject(cipher);
prompt.authenticate(
new BiometricPrompt.PromptInfo.Builder()
.setTitle("身份验证")
.setSubtitle("请验证指纹以继续操作")
.setNegativeButtonText("取消")
.build(),
cryptoObject);
assertTrue(latch.await(10, TimeUnit.SECONDS));
assertNull("认证过程不应报错", errorRef.get());
}
}
在模拟器上测试生物识别时,可以借助adb命令模拟指纹录入与触摸:
# 模拟触摸指纹传感器(适用于API 28以上模拟器) adb -e emu finger touch 1 # 模拟指纹认证失败 adb -e emu finger touch 2
finger touch 1会让模拟器认为第一个已录入的指纹通过了验证,从而触发onAuthenticationSucceeded回调;传入未录入的指纹ID则触发失败路径。这套命令配合上面的测试代码,可以稳定地跑通生物识别的正反向用例。
五、边界场景与常见坑点
认证测试中最容易遗漏的是边界场景,建议按下面这张清单逐项验证:
- 认证超时:解锁后等待超过有效期再使用密钥,应重新触发认证。
- 锁屏降级:设备未设置任何锁屏时,依赖
isDeviceSecure()的功能应给出明确引导而不是直接崩溃。 - 连续失败锁定:生物识别连续失败5次后,API 30以上设备会返回
BIOMETRIC_ERROR_LOCKED_OUT,应用需妥善处理并提示用户改用密码。 - 密钥失效:用户在系统设置中移除锁屏凭证后,Keystore中设置了认证要求的密钥会变成不可用状态,应用需要捕获
KeyPermanentlyInvalidatedException并引导重新初始化。 - 进程被杀:应用后台被系统回收后重新进入,认证状态是否正确恢复,Token是否需要重新刷新。
另一个高频坑点是多进程问题:如果应用有独立进程的组件也需要访问受保护数据,Keystore密钥的认证状态是设备级别的,但各进程的Token缓存是独立的,测试时要确保每个进程都能正确处理认证过期,而不是只测主进程。
最后,测试报告里建议明确记录设备型号、Android版本和安全补丁级别,因为不同厂商ROM对Keystore和生物识别的实现存在差异,同一套用例在Pixel和某些国产设备上的表现可能不一致,保留环境信息是后续排查问题的基础。
Android认证Authentication测试BiometricPrompt修改时间:2026-09-14 22:08:57