Jedną z metod ochrony informacji poufnych lub treści premium w aplikacji jest poproszenie o uwierzytelnienie biometryczne, np. za pomocą rozpoznawania twarzy lub odcisku palca. Z tego przewodnika dowiesz się, jak obsługiwać w aplikacji przepływy logowania biometrycznego.
Z zasady do pierwszego logowania na urządzeniu należy używać Menedżera danych logowania. Kolejne ponowne autoryzacje można przeprowadzić za pomocą BiometricPrompt lub Menedżera danych logowania. Zaletą korzystania z BiometricPrompt jest to, że oferuje więcej opcji dostosowywania, natomiast Menedżer danych logowania zapewnia jedną implementację w obu przepływach.
Deklarowanie typów uwierzytelniania obsługiwanych przez aplikację
Aby określić typy uwierzytelniania obsługiwane przez aplikację, użyj
BiometricManager.Authenticators
interfejsu. System umożliwia deklarowanie tych typów uwierzytelniania:
BIOMETRIC_STRONG- Uwierzytelnianie za pomocą biometrii klasy 3, zgodnie z definicją na stronie definicji zgodności z Androidem .
BIOMETRIC_WEAK- Uwierzytelnianie za pomocą biometrii klasy 2, zgodnie z definicją na stronie definicji zgodności z Androidem.
DEVICE_CREDENTIAL- Uwierzytelnianie za pomocą danych logowania blokady ekranu – kodu PIN, wzoru lub hasła użytkownika.
Aby zacząć korzystać z uwierzytelniania, użytkownik musi utworzyć kod PIN, wzór lub hasło. Jeśli użytkownik nie ma jeszcze takich danych, przepływ rejestracji biometrycznej poprosi go o ich utworzenie.
Aby określić typy uwierzytelniania biometrycznego akceptowane przez aplikację, przekaż
typ uwierzytelniania lub bitową kombinację typów do
setAllowedAuthenticators()
metody. Ten fragment kodu pokazuje, jak obsługiwać uwierzytelnianie za pomocą biometrii klasy 3 lub danych logowania blokady ekranu.
Kotlin
// Lets the user authenticate using either a Class 3 biometric or // their lock screen credential (PIN, pattern, or password). promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login for my app") .setSubtitle("Log in using your biometric credential") .setAllowedAuthenticators(BIOMETRIC_STRONG or DEVICE_CREDENTIAL) .build()
Java
// Lets user authenticate using either a Class 3 biometric or // their lock screen credential (PIN, pattern, or password). promptInfo = new BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login for my app") .setSubtitle("Log in using your biometric credential") .setAllowedAuthenticators(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) .build();
Te kombinacje typów uwierzytelniania nie są obsługiwane w
Androidzie 10 (interfejs API na poziomie 29) i starszych wersjach: DEVICE_CREDENTIAL oraz
BIOMETRIC_STRONG | DEVICE_CREDENTIAL. Aby sprawdzić, czy w Androidzie 10 i starszych wersjach jest ustawiony kod PIN, wzór lub hasło, użyj
KeyguardManager.isDeviceSecure()
metody.
Sprawdzanie, czy uwierzytelnianie biometryczne jest dostępne
Po określeniu elementów uwierzytelniania obsługiwanych przez aplikację sprawdź, czy są one dostępne. Aby to zrobić, przekaż do metody
canAuthenticate() tę samą bitową kombinację typów, którą zadeklarowano za pomocą metody
setAllowedAuthenticators().
W razie potrzeby wywołaj
ACTION_BIOMETRIC_ENROLL działanie intencji. W dodatku do intencji podaj zestaw uwierzytelnień akceptowanych przez aplikację. Ta intencja wyświetla użytkownikowi prośbę o zarejestrowanie danych logowania do uwierzytelniania akceptowanego przez aplikację.
Kotlin
val biometricManager = BiometricManager.from(this) when (biometricManager.canAuthenticate(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)) { BiometricManager.BIOMETRIC_SUCCESS -> Log.d("MY_APP_TAG", "App can authenticate using biometrics.") BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> Log.e("MY_APP_TAG", "No biometric features available on this device.") BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> Log.e("MY_APP_TAG", "Biometric features are currently unavailable.") BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> { // Prompts the user to create credentials that your app accepts. val enrollIntent = Intent(Settings.ACTION_BIOMETRIC_ENROLL).apply { putExtra(Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, BIOMETRIC_STRONG or DEVICE_CREDENTIAL) } startActivityForResult(enrollIntent, REQUEST_CODE) } }
Java
BiometricManager biometricManager = BiometricManager.from(this); switch (biometricManager.canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL)) { case BiometricManager.BIOMETRIC_SUCCESS: Log.d("MY_APP_TAG", "App can authenticate using biometrics."); break; case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE: Log.e("MY_APP_TAG", "No biometric features available on this device."); break; case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE: Log.e("MY_APP_TAG", "Biometric features are currently unavailable."); break; case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED: // Prompts the user to create credentials that your app accepts. final Intent enrollIntent = new Intent(Settings.ACTION_BIOMETRIC_ENROLL); enrollIntent.putExtra(Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, BIOMETRIC_STRONG | DEVICE_CREDENTIAL); startActivityForResult(enrollIntent, REQUEST_CODE); break; }
Określanie sposobu uwierzytelniania użytkownika
Gdy użytkownik się uwierzytelni, możesz sprawdzić, czy użył danych logowania urządzenia czy danych biometrycznych, wywołując metodę
getAuthenticationType().
Wyświetlanie prośby o zalogowanie się
Aby wyświetlić prośbę systemową o uwierzytelnienie za pomocą danych biometrycznych, użyj biblioteki Biometric. To okno systemowe jest spójne we wszystkich aplikacjach, które go używają, co zwiększa zaufanie użytkowników. Przykładowe okno dialogowe pokazano na rysunku 1.
Aby dodać uwierzytelnianie biometryczne do aplikacji za pomocą biblioteki Biometric:
W pliku
build.gradlemodułu aplikacji dodaj zależność odandroidx.biometricbiblioteki.W aktywności lub fragmencie, który zawiera okno logowania biometrycznego, wyświetl okno za pomocą logiki pokazanej w tym fragmencie kodu:
Kotlin
private lateinit var executor: Executor private lateinit var biometricPrompt: BiometricPrompt private lateinit var promptInfo: BiometricPrompt.PromptInfo override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) executor = ContextCompat.getMainExecutor(this) biometricPrompt = BiometricPrompt(this, executor, object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { super.onAuthenticationError(errorCode, errString) Toast.makeText(applicationContext, "Authentication error: $errString", Toast.LENGTH_SHORT) .show() } override fun onAuthenticationSucceeded( result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) Toast.makeText(applicationContext, "Authentication succeeded!", Toast.LENGTH_SHORT) .show() } override fun onAuthenticationFailed() { super.onAuthenticationFailed() Toast.makeText(applicationContext, "Authentication failed", Toast.LENGTH_SHORT) .show() } }) promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login for my app") .setSubtitle("Log in using your biometric credential") .setNegativeButtonText("Use account password") .build() // Prompt appears when user clicks "Log in". // Consider integrating with the keystore to unlock cryptographic operations, // if needed by your app. val biometricLoginButton = findViewById<Button>(R.id.biometric_login) biometricLoginButton.setOnClickListener { biometricPrompt.authenticate(promptInfo) } }
Java
private Executor executor; private BiometricPrompt biometricPrompt; private BiometricPrompt.PromptInfo promptInfo; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); executor = ContextCompat.getMainExecutor(this); biometricPrompt = new BiometricPrompt(MainActivity.this, executor, new BiometricPrompt.AuthenticationCallback() { @Override public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { super.onAuthenticationError(errorCode, errString); Toast.makeText(getApplicationContext(), "Authentication error: " + errString, Toast.LENGTH_SHORT) .show(); } @Override public void onAuthenticationSucceeded( @NonNull BiometricPrompt.AuthenticationResult result) { super.onAuthenticationSucceeded(result); Toast.makeText(getApplicationContext(), "Authentication succeeded!", Toast.LENGTH_SHORT).show(); } @Override public void onAuthenticationFailed() { super.onAuthenticationFailed(); Toast.makeText(getApplicationContext(), "Authentication failed", Toast.LENGTH_SHORT) .show(); } }); promptInfo = new BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login for my app") .setSubtitle("Log in using your biometric credential") .setNegativeButtonText("Use account password") .build(); // Prompt appears when user clicks "Log in". // Consider integrating with the keystore to unlock cryptographic operations, // if needed by your app. Button biometricLoginButton = findViewById(R.id.biometric_login); biometricLoginButton.setOnClickListener(view -> { biometricPrompt.authenticate(promptInfo); }); }
Używanie rozwiązania kryptograficznego, które zależy od uwierzytelniania
Aby dodatkowo chronić informacje poufne w aplikacji, możesz włączyć
kryptografię do przepływu uwierzytelniania biometrycznego za pomocą instancji
CryptoObject.
Framework obsługuje te obiekty kryptograficzne:
Signature,
Cipher i
Mac.
Gdy użytkownik pomyślnie się uwierzytelni za pomocą BiometricPrompt, aplikacja może wykonać operację kryptograficzną. Jeśli na przykład uwierzytelnienie nastąpi za pomocą obiektu
Cipher, aplikacja może następnie szyfrować i odszyfrowywać dane za pomocą obiektu
SecretKey.
W kolejnych sekcjach znajdziesz przykłady użycia obiektu Cipher i obiektu SecretKey do szyfrowania danych. Każdy przykład korzysta z tych metod:
Kotlin
private fun generateSecretKey(keyGenParameterSpec: KeyGenParameterSpec) { val keyGenerator = KeyGenerator.getInstance( KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") keyGenerator.init(keyGenParameterSpec) keyGenerator.generateKey() } private fun getSecretKey(): SecretKey { val keyStore = KeyStore.getInstance("AndroidKeyStore") // Before the keystore can be accessed, it must be loaded. keyStore.load(null) return keyStore.getKey(KEY_NAME, null) as SecretKey } private fun getCipher(): Cipher { return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7) }
Java
private void generateSecretKey(KeyGenParameterSpec keyGenParameterSpec) { KeyGenerator keyGenerator = KeyGenerator.getInstance( KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); keyGenerator.init(keyGenParameterSpec); keyGenerator.generateKey(); } private SecretKey getSecretKey() { KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); // Before the keystore can be accessed, it must be loaded. keyStore.load(null); return ((SecretKey)keyStore.getKey(KEY_NAME, null)); } private Cipher getCipher() { return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7); }
Uwierzytelnianie tylko za pomocą danych biometrycznych
Jeśli aplikacja używa klucza tajnego, który wymaga odblokowania danych biometrycznych, użytkownik musi uwierzytelnić swoje dane biometryczne za każdym razem , zanim aplikacja uzyska dostęp do klucza.
Aby szyfrować informacje poufne tylko wtedy, gdy użytkownik się uwierzytelni za pomocą danych biometrycznych:
Wygeneruj klucz, który używa tej
KeyGenParameterSpeckonfiguracji:Kotlin
generateSecretKey(KeyGenParameterSpec.Builder( KEY_NAME, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .setUserAuthenticationRequired(true) // Invalidate the keys if the user has registered a new biometric // credential, such as a new fingerprint. Can call this method only // on Android 7.0 (API level 24) or higher. The variable // "invalidatedByBiometricEnrollment" is true by default. .setInvalidatedByBiometricEnrollment(true) .build())
Java
generateSecretKey(new KeyGenParameterSpec.Builder( KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .setUserAuthenticationRequired(true) // Invalidate the keys if the user has registered a new biometric // credential, such as a new fingerprint. Can call this method only // on Android 7.0 (API level 24) or higher. The variable // "invalidatedByBiometricEnrollment" is true by default. .setInvalidatedByBiometricEnrollment(true) .build());
Uruchom przepływ uwierzytelniania biometrycznego, który zawiera szyfr:
Kotlin
biometricLoginButton.setOnClickListener { // Exceptions are unhandled within this snippet. val cipher = getCipher() val secretKey = getSecretKey() cipher.init(Cipher.ENCRYPT_MODE, secretKey) biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher)) }
Java
biometricLoginButton.setOnClickListener(view -> { // Exceptions are unhandled within this snippet. Cipher cipher = getCipher(); SecretKey secretKey = getSecretKey(); cipher.init(Cipher.ENCRYPT_MODE, secretKey); biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher)); });
W wywołaniach zwrotnych uwierzytelniania biometrycznego użyj klucza tajnego do zaszyfrowania informacji poufnych:
Kotlin
override fun onAuthenticationSucceeded( result: BiometricPrompt.AuthenticationResult) { val encryptedInfo: ByteArray = result.cryptoObject.cipher?.doFinal( // plaintext-string text is whatever data the developer would like // to encrypt. It happens to be plain-text in this example, but it // can be anything plaintext-string.toByteArray(Charset.defaultCharset()) ) Log.d("MY_APP_TAG", "Encrypted information: " + Arrays.toString(encryptedInfo)) }
Java
@Override public void onAuthenticationSucceeded( @NonNull BiometricPrompt.AuthenticationResult result) { // NullPointerException is unhandled; use Objects.requireNonNull(). byte[] encryptedInfo = result.getCryptoObject().getCipher().doFinal( // plaintext-string text is whatever data the developer would like // to encrypt. It happens to be plain-text in this example, but it // can be anything plaintext-string.getBytes(Charset.defaultCharset())); Log.d("MY_APP_TAG", "Encrypted information: " + Arrays.toString(encryptedInfo)); }
Uwierzytelnianie za pomocą danych biometrycznych lub danych blokady ekranu
Możesz użyć klucza tajnego, który umożliwia uwierzytelnianie za pomocą danych biometrycznych lub danych blokady ekranu (kodu PIN, wzoru lub hasła). Podczas konfigurowania tego klucza określ okres ważności. W tym okresie aplikacja może wykonywać wiele operacji kryptograficznych bez konieczności ponownego uwierzytelniania użytkownika.
Aby zaszyfrować informacje poufne po uwierzytelnieniu użytkownika za pomocą danych biometrycznych lub danych blokady ekranu:
Wygeneruj klucz, który używa tej
KeyGenParameterSpeckonfiguracji:Kotlin
generateSecretKey(KeyGenParameterSpec.Builder( KEY_NAME, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .setUserAuthenticationRequired(true) .setUserAuthenticationParameters(VALIDITY_DURATION_SECONDS, ALLOWED_AUTHENTICATORS) .build())
Java
generateSecretKey(new KeyGenParameterSpec.Builder( KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .setUserAuthenticationRequired(true) .setUserAuthenticationParameters(VALIDITY_DURATION_SECONDS, ALLOWED_AUTHENTICATORS) .build());
W ciągu
VALIDITY_DURATION_SECONDSsekund po uwierzytelnieniu użytkownika zaszyfruj informacje poufne:Kotlin
private fun encryptSecretInformation() { // Exceptions are unhandled for getCipher() and getSecretKey(). val cipher = getCipher() val secretKey = getSecretKey() try { cipher.init(Cipher.ENCRYPT_MODE, secretKey) val encryptedInfo: ByteArray = cipher.doFinal( // plaintext-string text is whatever data the developer would // like to encrypt. It happens to be plain-text in this example, // but it can be anything plaintext-string.toByteArray(Charset.defaultCharset())) Log.d("MY_APP_TAG", "Encrypted information: " + Arrays.toString(encryptedInfo)) } catch (e: InvalidKeyException) { Log.e("MY_APP_TAG", "Key is invalid.") } catch (e: UserNotAuthenticatedException) { Log.d("MY_APP_TAG", "The key's validity timed out.") biometricPrompt.authenticate(promptInfo) }
Java
private void encryptSecretInformation() { // Exceptions are unhandled for getCipher() and getSecretKey(). Cipher cipher = getCipher(); SecretKey secretKey = getSecretKey(); try { // NullPointerException is unhandled; use Objects.requireNonNull(). ciper.init(Cipher.ENCRYPT_MODE, secretKey); byte[] encryptedInfo = cipher.doFinal( // plaintext-string text is whatever data the developer would // like to encrypt. It happens to be plain-text in this example, // but it can be anything plaintext-string.getBytes(Charset.defaultCharset())); } catch (InvalidKeyException e) { Log.e("MY_APP_TAG", "Key is invalid."); } catch (UserNotAuthenticatedException e) { Log.d("MY_APP_TAG", "The key's validity timed out."); biometricPrompt.authenticate(promptInfo); } }
Uwierzytelnianie za pomocą kluczy autoryzacji do użycia
W instancji
BiometricPrompt możesz obsługiwać klucze autoryzacji do użycia. Taki klucz wymaga, aby użytkownik za każdym razem, gdy aplikacja musi uzyskać dostęp do danych chronionych tym kluczem, podał dane biometryczne lub dane urządzenia. Klucze autoryzacji do użycia mogą być przydatne w przypadku transakcji o wysokiej wartości, takich jak dokonywanie dużej płatności lub aktualizowanie dokumentacji medycznej.
Aby powiązać obiekt BiometricPrompt z kluczem autoryzacji do użycia, dodaj kod podobny do tego:
Kotlin
val authPerOpKeyGenParameterSpec = KeyGenParameterSpec.Builder("myKeystoreAlias", key-purpose) // Accept either a biometric credential or a device credential. // To accept only one type of credential, include only that type as the // second argument. .setUserAuthenticationParameters(0 /* duration */, KeyProperties.AUTH_BIOMETRIC_STRONG or KeyProperties.AUTH_DEVICE_CREDENTIAL) .build()
Java
KeyGenParameterSpec authPerOpKeyGenParameterSpec = new KeyGenParameterSpec.Builder("myKeystoreAlias", key-purpose) // Accept either a biometric credential or a device credential. // To accept only one type of credential, include only that type as the // second argument. .setUserAuthenticationParameters(0 /* duration */, KeyProperties.AUTH_BIOMETRIC_STRONG | KeyProperties.AUTH_DEVICE_CREDENTIAL) .build();
Uwierzytelnianie bez wyraźnego działania użytkownika
Domyślnie system wymaga od użytkowników wykonania określonej czynności, np. naciśnięcia przycisku, po zaakceptowaniu ich danych biometrycznych. Ta konfiguracja jest preferowana, jeśli aplikacja wyświetla okno dialogowe w celu potwierdzenia działania wymagającego zachowania ostrożności lub obarczonego wysokim ryzykiem, np. dokonania zakupu.
Jeśli jednak aplikacja wyświetla okno uwierzytelniania biometrycznego w przypadku działania o niższym ryzyku, możesz podać systemowi wskazówkę, że użytkownik nie musi potwierdzać uwierzytelniania. Ta wskazówka może umożliwić użytkownikowi szybsze wyświetlanie treści w aplikacji po ponownym uwierzytelnieniu za pomocą pasywnej metody, takiej jak rozpoznawanie twarzy lub tęczówki. Aby podać tę wskazówkę, przekaż wartość false do metody
setConfirmationRequired().
Na rysunku 2 pokazano 2 wersje tego samego okna. Jedna wersja wymaga wyraźnego działania użytkownika, a druga nie.
Ten fragment kodu pokazuje, jak wyświetlić okno, które nie wymaga wyraźnego działania użytkownika, aby zakończyć proces uwierzytelniania:
Kotlin
// Lets the user authenticate without performing an action, such as pressing a // button, after their biometric credential is accepted. promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login for my app") .setSubtitle("Log in using your biometric credential") .setNegativeButtonText("Use account password") .setConfirmationRequired(false) .build()
Java
// Lets the user authenticate without performing an action, such as pressing a // button, after their biometric credential is accepted. promptInfo = new BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login for my app") .setSubtitle("Log in using your biometric credential") .setNegativeButtonText("Use account password") .setConfirmationRequired(false) .build();
Zezwalanie na powrót do danych logowania innych niż biometryczne
Jeśli chcesz, aby aplikacja umożliwiała uwierzytelnianie za pomocą danych biometrycznych lub danych
urządzenia, możesz zadeklarować, że Twoja aplikacja obsługuje dane
urządzenia, dodając
DEVICE_CREDENTIAL do zestawu wartości przekazywanych do
setAllowedAuthenticators().
Jeśli aplikacja używa obecnie
createConfirmDeviceCredentialIntent()
lub setDeviceCredentialAllowed()
do zapewnienia tej funkcji, przejdź na setAllowedAuthenticators().
Dodatkowe materiały
Więcej informacji o uwierzytelnianiu biometrycznym w Androidzie znajdziesz w tych materiałach.