Jedną z metod ochrony informacji poufnych lub treści premium w aplikacji jest żądanie uwierzytelniania biometrycznego, np. za pomocą rozpoznawania twarzy lub odcisków palców. Z tego przewodnika dowiesz się, jak obsługiwać w aplikacji procesy logowania biometrycznego.
Zasadniczo podczas pierwszego logowania na urządzeniu należy użyć Menedżera danych logowania. Kolejne ponowne autoryzacje można wykonać za pomocą promptu biometrycznego lub Menedżera danych logowania. Zaletą korzystania z promptu biometrycznego jest to, że oferuje on więcej opcji dostosowywania, podczas gdy Menedżer danych logowania zapewnia jedno wdrożenie w ramach obu procesów.
Deklarowanie typów uwierzytelniania obsługiwanych przez aplikację
Aby określić typy uwierzytelniania obsługiwane przez aplikację, użyj interfejsu BiometricManager.Authenticators
. System umożliwia deklarowanie tych typów uwierzytelniania:
BIOMETRIC_STRONG
- Uwierzytelnianie za pomocą danych biometrycznych klasy 3 zgodnie z definicją na stronie Definicjja zgodności z Androidem.
BIOMETRIC_WEAK
- Uwierzytelnianie za pomocą danych biometrycznych klasy 2 zgodnie z definicją na stronie definicji zgodności z Androidem.
DEVICE_CREDENTIAL
- Uwierzytelnianie za pomocą danych logowania do 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 konta, proces rejestracji biometrycznej poprosi go o jego utworzenie.
Aby określić typy uwierzytelniania biometrycznego akceptowane przez aplikację, prześlij typ uwierzytelniania lub kombinację typów bitowych do metody setAllowedAuthenticators()
. Ten fragment kodu pokazuje, jak obsługiwać uwierzytelnianie za pomocą danych biometrycznych klasy 3 lub danych uwierzytelniających 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();
Na Androidzie 10 (poziom interfejsu API 29) i starszych te kombinacje typów uwierzytelniania nie są obsługiwane: DEVICE_CREDENTIAL
i BIOMETRIC_STRONG | DEVICE_CREDENTIAL
. Aby sprawdzić, czy na urządzeniu z Androidem 10 lub starszym jest ustawiony kod PIN, wzór lub hasło, użyj metody KeyguardManager.isDeviceSecure()
.
Sprawdź, czy uwierzytelnianie biometryczne jest dostępne
Po wybraniu elementów uwierzytelniania, które obsługuje Twoja aplikacja, sprawdź, czy są one dostępne. Aby to zrobić, prześlij tę samą kombinację typów bitowych, która została zadeklarowana za pomocą metody setAllowedAuthenticators()
, do metody canAuthenticate()
.
W razie potrzeby wywołaj działanie ACTION_BIOMETRIC_ENROLL
. W dodatkowych danych intencji podaj zestaw uwierzytelniania, który akceptuje Twoja aplikacja. Ta intencja powoduje wyświetlenie użytkownikowi prośby o zarejestrowanie danych logowania w urządzeniu uwierzytelniającym, które akceptuje Twoja aplikacja.
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 uwierzytelnienia użytkownika
Po uwierzytelnieniu użytkownika możesz sprawdzić, czy uwierzytelnił się on za pomocą danych logowania na urządzeniu lub danych biometrycznych, wywołując funkcję getAuthenticationType()
.
Wyświetlanie prośby o logowanie
Aby wyświetlić komunikat systemowy z prośbą o uwierzytelnienie użytkownika za pomocą danych uwierzytelniających biometrycznych, użyj biblioteki biometrycznej. Ten systemowy dialog jest spójny w przypadku wszystkich aplikacji, które go używają, co zwiększa zaufanie użytkowników. Rysunek 1 przedstawia przykładowe okno.
Aby dodać uwierzytelnianie biometryczne do aplikacji za pomocą biblioteki biometrycznej:
W pliku
build.gradle
modułu aplikacji dodaj zależność od bibliotekiandroidx.biometric
.W aktywności lub fragmencie, który zawiera okno logowania biometrycznego, wyświetl okno, używając 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żyj rozwiązania kryptograficznego, które zależy od uwierzytelniania.
Aby jeszcze lepiej chronić informacje wrażliwe w aplikacji, możesz włączyć szyfrowanie do procesu uwierzytelniania biometrycznego za pomocą instancji CryptoObject
.
Platforma obsługuje te obiekty kryptograficzne: Signature
, Cipher
i Mac
.
Gdy użytkownik uwierzytelni się za pomocą promptu biometrycznego, aplikacja może wykonać operację kryptograficzną. Jeśli na przykład uwierzytelniasz się za pomocą obiektu Cipher
, Twoja aplikacja może szyfrować i odszyfrowywać za pomocą obiektu SecretKey
.
W kolejnych sekcjach znajdziesz przykłady korzystania z 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); }
uwierzytelniać się tylko za pomocą danych uwierzytelniających biometrycznych;
Jeśli Twoja aplikacja używa klucza tajnego, który wymaga odblokowania za pomocą 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 uwierzytelnia się za pomocą danych logowania biometrycznych, wykonaj te czynności:
Wygeneruj klucz, który używa tej konfiguracji:
KeyGenParameterSpec
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());
Aby rozpocząć proces uwierzytelniania biometrycznego, który wykorzystuje 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 funkcji 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)); }
uwierzytelniać się za pomocą danych biometrycznych lub danych uwierzytelniających na ekranie blokady;
Możesz użyć klucza tajnego, który umożliwia uwierzytelnianie za pomocą danych logowania biometrycznych lub danych logowania do ekranu blokady (kodu PIN, wzoru lub hasła). Podczas konfigurowania tego klucza określ okres ważności. W tym czasie aplikacja może wykonywać wiele operacji kryptograficznych bez konieczności ponownego uwierzytelniania użytkownika.
Aby zaszyfrować poufne informacje po uwierzytelnieniu użytkownika za pomocą danych biometrycznych lub danych logowania do ekranu blokady:
Wygeneruj klucz, który używa tej konfiguracji:
KeyGenParameterSpec
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_SECONDS
minut od uwierzytelnienia użytkownika zaszyfruj poufne informacje: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 przy użyciu kluczy uwierzytelniających na potrzeby pojedynczego użycia
Możesz zapewnić obsługę kluczy autoryzujących w ramach instancji BiometricPrompt
. Taki klucz wymaga od użytkownika przedstawienia danych biometrycznych lub danych logowania urządzenia za każdym razem, gdy aplikacja potrzebuje dostępu do danych chronionych przez ten klucz. Klucze uwierzytelniające mogą być przydatne w przypadku transakcji o wysokiej wartości, takich jak dokonywanie dużych płatności lub aktualizowanie danych medycznych.
Aby powiązać obiekt BiometricPrompt
z kluczem autoryzacji na potrzeby 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ślonego działania, np. naciśnięcia przycisku, po zaakceptowaniu danych biometrycznych. Ta konfiguracja jest preferowana, jeśli aplikacja wyświetla okno dialogowe, aby potwierdzić działanie o charakterze wrażliwym lub obarczonym wysokim ryzykiem, np. dokonanie zakupu.
Jeśli jednak Twoja aplikacja wyświetla okno uwierzytelniania biometrycznego w przypadku działania o mniejszym ryzyku, możesz podać systemowi podpowiedź, że użytkownik nie musi potwierdzać uwierzytelniania. Ten podpowiedź może pozwolić użytkownikowi szybciej wyświetlić treści w aplikacji po ponownym uwierzytelnieniu za pomocą metody pasywnej, takiej jak rozpoznawanie twarzy lub tęczówki. Aby podać ten podpowiedź, przekaż wartość false
do metody setConfirmationRequired()
.
Rysunek 2 przedstawia 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 dialogowe, które nie wymaga od użytkownika wyraźnego działania w celu zakończenia procesu 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 stosowanie danych logowania niebędących danymi biometrycznymi
Jeśli chcesz, aby Twoja aplikacja umożliwiała uwierzytelnianie za pomocą danych biometrycznych lub danych logowania na urządzeniu, możesz zadeklarować, że aplikacja obsługuje dane logowania na urządzeniu, podając wartość DEVICE_CREDENTIAL
w zbiorze wartości przekazywanych do funkcji setAllowedAuthenticators()
.
Jeśli Twoja aplikacja używa obecnie createConfirmDeviceCredentialIntent()
lub setDeviceCredentialAllowed()
, aby zapewnić tę funkcję, przełącz się na setAllowedAuthenticators()
.
Dodatkowe materiały
Więcej informacji o uwierzytelnianiu biometrycznym na urządzeniach z Androidem znajdziesz w tych materiałach.