本文是 Android 生物辨认身份验证系列文章的第二篇, 上篇文章 次要通过比拟传统用户名和明码的认证形式和生物辨认身份认证形式的不同,以及介绍生物辨认加密的不同加密形式,来向开发者展现为何须要在利用中应用生物辨认身份认证技术。
为了拓展传统的登录受权流程,使其反对生物辨认身份验证,您能够在用户胜利登录之后提醒用户启用生物辨认身份验证。图 1A 展现了一个典型的登录流程,您可能曾经很相熟了。当用户点击登录按钮,且利用获取到服务器返回的 userToken 之后,再提醒用户是否启用,如图 1B 所示。一旦启用,每次用户须要登录时,利用都该当自动弹出生物辨认身份验证对话框,如图 2 所示。
△ 图 1A: 典型的登录界面
△ 图 1B: 启用生物辨认身份验证
△ 图 2: 确认应用生物辨认身份验证进行登录
在图 2 中的界面有一个确定按钮,实际上该按钮是可选的。举个例子,如果您开发的是一个餐厅的利用,倡议显示该按钮,因为能够应用生物辨认身份验证的形式让顾客领取用餐费用。对于敏感的交易和领取,咱们建议您要求用户进行确认。若要在界面中蕴含此确认按钮,您能够在构建 BiometricPrompt.PromptInfo 时调用 setConfirmationRequired(true) 即可。这里要留神的是,如果您不调用 setConfirmationRequired(true)),零碎会默认将其设置为 true。
接入生物辨认的设计流程
示例中的代码应用了带有 CryptoObject 实例的加密版 BiometricPrompt。
如果您的利用须要认证,那么您就应该创立一个专门的 LoginActivity 组件作为利用的登录界面。无论利用要求进行身份验证的频率多高,只有须要验证,就应该这么做。若用户之前已认证过,那么 LoginActivity 将调用 finish() 办法,让用户持续应用。如果用户还没有进行身份验证,那么您应该查看生物辨认身份验证是否启用。
有很多办法来查看是否启用了生物辨认。与其在各种不同的代替计划中周旋,不如咱们间接深入研究一个特地的办法: 间接查看自定义属性 ciphertextWrapper
是否是 null。当用户在您的利用中启用生物辨认身份验证后,您就能够创立一个 CiphertextWrapper
数据类,来将加密后的 userToken
(也就是 ciphertext) 存储在 SharedPreferences
或 Room 这样的持久性存储中。因而,若 ciphertextWrapper
不是 null,就相当于您领有了拜访近程服务所需的已加密的 userToken
,这也意味着以后生物辨认已启用。
if (ciphertextWrapper != null) {// 用户已启用了生物辨认} else {// 生物辨认未启用}
若生物辨认未被启用,则用户能够单击 (如图 1B 所示) 以启用它,这时您将向用户展现生物辨认身份验证提示框,如图 3 所示。
如下代码示例中,showBiometricPromptForEncryption()
展现了如何设置与 BiometricPrompt 关联的加密密钥。实质上,就是从一个 String
初始化出一个 Cipher
,而后将该 Cipher
传递给 CryptoObject
。最初再将 CryptoObject
传递给 biometricPrompt.authenticate(promptInfo, cryptoObject)
办法。
binding.useBiometrics.setOnClickListener {showBiometricPromptForEncryption()
}
....
private fun showBiometricPromptForEncryption() {val canAuthenticate = BiometricManager.from(applicationContext).canAuthenticate()
if (canAuthenticate == BiometricManager.BIOMETRIC_SUCCESS) {
val secretKeyName = SECRET_KEY_NAME
cryptographyManager = CryptographyManager()
val cipher = cryptographyManager.getInitializedCipherForEncryption(secretKeyName)
val biometricPrompt =
BiometricPromptUtils.createBiometricPrompt(this, ::encryptAndStoreServerToken)
val promptInfo = BiometricPromptUtils.createPromptInfo(this)
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
}
}
△ 图 3: 激活生物辨认的提醒
在图 2 和图 3 所示的场景下,利用只有 userToken
这个数据。然而除非用户每次关上利用都要输出一次明码,否则该 userToken
就须要长久化用于之后的会话。然而,如果您间接存储了未加密的 userToken
,那么攻击者就可能侵入设施读取明文的 userToken
,而后应用它从近程服务器上获取数据。因而,在将 userToken
保留到本地之前,最好先将其加密,这就是图 3 中 BiometricPrompt 的作用。当用户应用生物辨认验证身份后,您的指标是应用 BiometricPrompt 解锁密钥 (能够应用 auth-per-use 密钥,也可应用 time-bound 密钥 ),而后用该密钥对服务器生成的 userToken 进行加密,再将其保留到本地。自此,当用户须要登录时,就能够应用生物辨认验证身份 (即生物辨认认证 -> 解锁密钥 -> 解密 userToken 进行数据拜访)。
这里要留神辨别用户是第一次启用生物辨认,还是在应用生物辨认进行登录。启用生物辨认时,利用调用 showBiometricPromptForEncryption()
办法,该办法会初始化一个 Cipher
用于加密 userToken
。另一方面,若用户是在应用生物辨认进行登录,那应该调用 showBiometricPromptForDecryption()
办法,它会初始化一个用于解密的 Cipher
,再应用该 Cipher
来解密 userToken
。
启用生物辨认之后,用户下次返回利用时,会通过生物辨认身份验证对话框进行认证,如图 4 所示。请留神,因为图 4 是用于登录利用,而图 2 是用于确定交易的,所以在图 4 中没有确认按钮,因为登录行为是一个被动的、易逆向复原的行为。
△ 图 4
若要为您的用户实现这一流程,当您的 LoginActivity
实现认证过程后,应用胜利通过 BiometricPrompt 认证解锁的加密对象来解密 userToken
,而后在 LoginActivity
中调用 finish()
办法。
override fun onResume() {super.onResume()
if (ciphertextWrapper != null) {if (SampleAppUser.fakeToken == null) {showBiometricPromptForDecryption()
} else {
// 用户曾经胜利登录,因而间接进入接下来的利用流程
// 之后的就交给开发者您来实现了
updateApp(getString(R.string.already_signedin))
}
}
}
....
private fun showBiometricPromptForDecryption() {
ciphertextWrapper?.let { textWrapper ->
val canAuthenticate = BiometricManager.from(applicationContext).canAuthenticate()
if (canAuthenticate == BiometricManager.BIOMETRIC_SUCCESS) {val secretKeyName = getString(R.string.secret_key_name)
val cipher = cryptographyManager.getInitializedCipherForDecryption(secretKeyName, textWrapper.initializationVector)
biometricPrompt =
BiometricPromptUtils.createBiometricPrompt(
this,
::decryptServerTokenFromStorage
)
val promptInfo = BiometricPromptUtils.createPromptInfo(this)
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
}
}
}
private fun decryptServerTokenFromStorage(authResult: BiometricPrompt.AuthenticationResult) {
ciphertextWrapper?.let { textWrapper ->
authResult.cryptoObject?.cipher?.let {
val plaintext =
cryptographyManager.decryptData(textWrapper.ciphertext, it)
SampleAppUser.fakeToken = plaintext
// 当初您有了 token,就能够查问服务器上的其余数据了
// 咱们之所以称这个为 fakeToken,是因为它并不是真正从服务器中获取到的
// 在实在场景下,您会在从服务器上获取到 token 数据
// 此时,它能力算是一个真正的 token
updateApp(getString(R.string.already_signedin))
}
}
}
残缺的蓝图
图 5 展现了一个残缺的工程设计流程图,这也是咱们所举荐的流程。既然您在理论编码过程中可能会在很多中央偏离此流程,例如,您所应用的加密解决方案中解锁密钥可能只会用于加密而不用于解密,然而在这里咱们依然心愿可能通过提供这样一个残缺的示例为可能须要的开发者们提供帮忙。
但凡图中提到 密钥 的中央,您都能够依照需要应用 auth-per-use 密钥或是 time-bound 密钥。另外,但凡图中提到的 “ 利用中的存储系统 ” 的中央,您也都能够将其了解为您所偏爱的结构化存储: SharedPreferences
、Room
或是任何别的存储计划。最初,对于 userToken 您能够将其了解为一个令牌,有了它就能够去服务器上拜访被爱护的用户数据。服务器通常会将这种令牌作为调用方已被受权的证据。
图中的 “ 对 userToken 进行加密 ” 的箭头很可能会指向 “ 登录实现 ”,而不是回到 “LoginActivity”。尽管如此,咱们还是在图中让其指向了 “LoginActivity”,就是为了揭示大家留神,在用户点击 “ 激活生物辨认 ” 后,能够应用一个额定的 Activity (例如 EnableBiometricAuthActivity),使代码更加模块化,更具可读性。或者,您也能够创立带有两个 Fragment 的 LoginActivity: 一个 Fragments 用于理论的认证流程,另一个用来响应用户点击 “ 启用生物辨认 ”。
除了上面这个流程图之外,咱们还公布了一个设计指南,您能够在设计利用时进行参考。另外,咱们 在 Github 上的示例代码 心愿也可能帮忙您更好地了解如何应用生物辨认身份验证技术。
△ 图 5: 应用生物辨认同服务器获取受权的残缺蓝图
总结
在本篇文章中,咱们介绍了:
- 如何扩大 UI 来反对生物辨认身份验证;
- 针对生物辨认身份验证的流程,您的利用应着重解决的关键点是什么;
- 如何设计您的代码来解决生物辨认认证的不同场景;
- 登录流程的残缺工程设计图。
祝您编码欢快!