This article is the second article in a series of Android biometric authentication. Last article mainly compares the traditional user name and password authentication methods and the different biometric authentication methods, and introduces the different encryption methods of biometric encryption , To show developers why they need to use biometric authentication technology in applications.
In order to expand the traditional login authorization process to support biometric authentication, you can prompt the user to enable biometric authentication after the user successfully logs in. Figure 1A shows a typical login process, which you may already be familiar with. When the user clicks the login button and the application obtains the userToken returned by the server, it will prompt the user whether to enable it, as shown in Figure 1B. Once enabled, the application should automatically pop up the biometric authentication dialog box every time the user needs to log in, as shown in Figure 2.
△ Figure 1A: Typical login interface
△ Figure 1B: Enable biometric authentication
△ Figure 2: Confirmation to log in using biometric authentication
The interface in Figure 2 has an OK button, which is actually optional. For example, if you are developing a restaurant application, it is recommended to display this button, because you can use biometric authentication to allow customers to pay for meals. For sensitive transactions and payments, we recommend that you ask users to confirm. To include this confirmation button in the interface, you can call setConfirmationRequired(true) when BiometricPrompt.PromptInfo It should be noted here that if you do not call setConfirmationRequired(true) ), the system will set it to true by default.
access to biometric design process
The code in the example uses the encrypted version of BiometricPrompt with a CryptoObject instance.
If your application requires authentication, then you should create a dedicated LoginActivity component as the application's login interface. No matter how often the application requires authentication, it should be done as long as authentication is required. If the user has been authenticated before, then LoginActivity will call the finish() method to allow the user to continue using it. If the user has not been authenticated, then you should check whether biometric authentication is enabled.
There are many ways to check whether biometrics is enabled. Rather than dealing with various alternatives, let's go deep into a special method: directly check ciphertextWrapper
is null. After users enable biometric authentication in your application, you can create a CiphertextWrapper
data class to store the encrypted userToken
(that is, ciphertext) in persistent storage such as SharedPreferences
or Room Therefore, if ciphertextWrapper
is not null, it means that you have the encrypted userToken
needed to access the remote service, which also means that the current biometric identification is enabled.
if (ciphertextWrapper != null) {
// 用户已启用了生物识别
} else {
// 生物识别未启用
}
If the biometric is not enabled, the user can click (as shown in Figure 1B) to enable it, and then you will show the user a biometric authentication prompt box, as shown in Figure 3.
In the following code example, showBiometricPromptForEncryption()
shows how to set the encryption key associated with BiometricPrompt. Essentially, it is to String
from a Cipher
, and then pass Cipher
CryptoObject
. Then the final CryptoObject
passed to biometricPrompt.authenticate(promptInfo, cryptoObject)
method.
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))
}
}
△ Figure 3: Prompt to activate biometrics
In the scenarios shown in Figure 2 and Figure 3, the application only userToken
the data 060cc0c3e0de33. But unless the user has to enter the password every time he opens the application, the userToken
needs to be persisted for subsequent sessions. However, if you directly store the unencrypted userToken
, the attacker may intrude into the device to read the plaintext userToken
, and then use it to obtain data from the remote server. Therefore, before saving userToken
locally, it is best to encrypt it first. This is the role of BiometricPrompt in Figure 3. When the user uses biometric authentication, your goal is to use the BiometricPrompt to unlock the key (you can use the auth-per-use key, or you can use the time-bound key), and then use the key to pair the server The generated userToken is encrypted and then saved locally. Since then, when users need to log in, they can use biometric authentication (ie biometric authentication -> unlock key -> decrypt userToken for data access).
Here you should pay attention to distinguish whether the user is using biometrics for the first time or is using biometrics to log in. When biometric recognition is enabled, the application calls the showBiometricPromptForEncryption()
method, which will initialize a Cipher
to encrypt userToken
. On the other hand, if the user is using biometrics to log in, the showBiometricPromptForDecryption()
method should be called, which will initialize a Cipher
for decryption, and then use the Cipher
to decrypt userToken
.
After enabling biometrics, the next time the user returns to the application, the user will be authenticated through the biometric authentication dialog box, as shown in Figure 4. Please note that since Figure 4 is used to log in to the application, and Figure 2 is used to confirm the transaction, there is no confirmation button in Figure 4 because the login behavior is a passive and reversible behavior.
△ Picture 4
To implement this process for your users, after your LoginActivity
completes the authentication process, use the encrypted object successfully unlocked by BiometricPrompt authentication to decrypt userToken
, and then call the finish()
method LoginActivity
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))
}
}
}
complete blueprint
Figure 5 shows a complete engineering design flow chart, which is also our recommended process. Since you may deviate from this process in many places during the actual encoding process, for example, the unlocking key in the encryption solution you use may only be used for encryption and not for decryption, but here we still hope to be able to provide such A complete example provides help for developers who may need it.
key is mentioned in the figure, you can use the auth-per-use key or the time-bound key according to your needs. In addition, wherever the "storage system in application" mentioned in the figure, you can also understand it as your preferred structured storage: SharedPreferences
, Room
or any other storage solution. Finally, for userToken you can understand it as a token, with it you can go to the server to access the protected user data. The server usually uses this token as proof that the caller has been authorized.
The arrow of "encrypt userToken" in the figure is likely to point to "login complete" instead of returning to "LoginActivity". Nevertheless, we still point it to "LoginActivity" in the figure to remind everyone that after the user clicks "Activate Biometrics", an additional Activity (such as EnableBiometricAuthActivity) can be used to make the code more modular and more Readable. Alternatively, you can also create a LoginActivity with two Fragments: one Fragments for the actual authentication process, and the other to respond to the user's click on "Enable Biometrics".
In addition to the flow chart below, we have also released a design guide that you can refer to when designing your application. In addition, we sample code on Github's hope can help you better understand how to use biometric authentication technology.
△ Figure 5: The complete blueprint of using biometrics to obtain authorization from the same server
summary
In this article, we introduced:
- How to extend the UI to support biometric authentication;
- Regarding the process of biometric authentication, what are the key points that your application should focus on solving;
- How to design your code to handle different scenarios of biometric authentication;
- A complete engineering design drawing of the login process.
Happy coding!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。