Биометрическая аутентификация

Последнее обновление: 21.04.2024

Многие устройства Android активно используют сенсорные датчики для различных задач, прежде всего для идентификации пользователя. И на уровне Jetpack Compose мы тоже можем задействовать эти возможности. Ключевыми компонентами биометрической аутентификации являются классы BiometricManager и BiometricPrompt. BiometricManager позволяет проверить, что устройство поддерживает биометрическую аутентификацию, и что пользователь включил необходимые параметры аутентификации (например, отпечатки пальцев или распознавание лица). А класс BiometricPrompt позволяет отобразить стандартное диалоговое окно, которое помогает пользователю пройти процесс аутентификации, выполнить аутентификацию и сообщить приложению результатов операции аутентификации.

Настройка проекта

Прежде всего надо учитывать, что для работы с биометрией уровень API должен быть как минимум 29 (Android 10). Поэтому перейдем в файл build.gradle.kts (Module :app) и изменим в нем значение android/defaultConfig/minSdk на 29:

.............................................
android {
    namespace = "com.example.helloapp"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.example.helloapp"
        minSdk = 29  // изменим минимальный уровень API на 29
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

........................................        

Кроме того, добавим необходимые зависимости в проект. Для этого изменим файл libs.version.toml изменим следующим образом:

[versions]
biometric = "1.2.0-alpha05"

........................

[libraries]
androidx-biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" }

.......................

Затем в файл build.gradle.kts (Module :app) в секцию dependencies добавим следующую директиву:

dependencies {

    implementation(libs.androidx.biometric)
    ..........................................

После этого нажмем на кнопку "Sync Now" для синхронизации проекта.

Настройка разрешений

Для поддержки аутентификации по отпечатку пальца и лицу требуется, чтобы приложение запрашивало разрешения USE_BIOMETRIC и CAMERA, а также функцию android.hardware.camera. Для этого перейдем к файлу манифеста AndroidManifest.xml и добавим в него соответствующие разрешения:

<?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.biometricdemo" >

 
     <uses-feature android:name="android.hardware.camera" android:required="false" / >
     <uses-permission android:name="android.permission.USE_BIOMETRIC" / >
     <uses-permission android:name="android.permission.CAMERA" / >

.............................................................................

Пример биометрической аутентификации

Сначала определим все приложение, которое будет представлять следующий код:

package com.example.helloapp

import android.os.Bundle
import android.widget.Toast
import androidx.activity.compose.setContent
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.fragment.app.FragmentActivity


class MainActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            AuthenticationScreen()
        }
    }
}

@Composable
fun AuthenticationScreen() {
    var supportsBiometrics by remember { mutableStateOf(false) }
    val context = LocalContext.current as FragmentActivity
    val biometricManager = BiometricManager.from(context)

    supportsBiometrics = when (biometricManager.canAuthenticate(
        BiometricManager.Authenticators.BIOMETRIC_STRONG)) {
        BiometricManager.BIOMETRIC_SUCCESS -> true
        else -> {
            Toast.makeText(context, "Биометрия недоступна", Toast.LENGTH_LONG).show()
            false
        }
    }

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Button(
            enabled = supportsBiometrics,
            onClick = {
                authenticate(context)
            },
            modifier = Modifier.padding(8.dp)
        ) {
            Text("Аутентификация", fontSize = 22.sp)
        }
    }
}

fun authenticate(context: FragmentActivity) {

    val executor = context.mainExecutor
    val biometricPrompt = BiometricPrompt(
        context,
        executor,
        object : BiometricPrompt.AuthenticationCallback() {

            override fun onAuthenticationSucceeded(
                result: BiometricPrompt.AuthenticationResult) {
                Toast.makeText(context, "Аутентифкация пройдена", Toast.LENGTH_LONG).show()
            }

            override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                Toast.makeText(context, "Ошибка при аутентификации: $errString", Toast.LENGTH_LONG).show()
            }

            override fun onAuthenticationFailed() {
                Toast.makeText(context, "Не удалось пройти аутентификацию", Toast.LENGTH_LONG).show()
            }
        })

    val promptInfo = BiometricPrompt.PromptInfo.Builder()
        .setTitle("Биометрическая аутентификация")
        .setDescription("Используйте отпечаток пальца или камеру для аутентификации")
        .setNegativeButtonText("Отмена")
        .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
        .build()

    biometricPrompt.authenticate(promptInfo)
}

В кратце разберем этот код. Прежде всего в качестве класса Activity здесь используется не стандартный ComponentActivity, а FragmentActivity:

class MainActivity : FragmentActivity() {

Дело в том, что класс ComponentActivity (на момент написания статьи) несовместим с BiometricPrompt, который применяется для отображения диалогового окна с подтверждением разрешений. Чтобы обойти эту проблему, нам нужно вместо этого создать подкласс MainActivity от класса FragmentActivity.

В качестве основного компонента, где производятся все действия, определен компонент AuthenticationScreen.

@Composable
fun AuthenticationScreen() {
    var supportsBiometrics by remember { mutableStateOf(false) }
    val context = LocalContext.current as FragmentActivity
    val biometricManager = BiometricManager.from(context)

    supportsBiometrics = when (biometricManager.canAuthenticate(
        BiometricManager.Authenticators.BIOMETRIC_STRONG)) {
        BiometricManager.BIOMETRIC_SUCCESS -> true
        else -> {
            Toast.makeText(context, "Биометрия недоступна", Toast.LENGTH_LONG).show()
            false
        }
    }

Для отслеживания доступности биометрии компонент определяет переменную supportsBiometrics. С помощью свойства LocalContext.current компонент получает доступ к локальному контексту - текущему объекту Activity (в нашем случае FragmentActivity) и используют его для получения ссылки на объект BiometricManager. Затем у полученного объекта BiometricManager выполняется вызов метода canAuthenticate(), который проверяет доступность биометрии для текущего пользователя. И если аутентификация недоступна, отображается всплывающее сообщение. Если биометрия доступна, то в supportsBiometrics помещается значение true, и пользователь может пройти биометрическую аутентификацию.

Весь интерфейс компонента по сути состоит из одной кнопки:

Button(
    enabled = supportsBiometrics,
    onClick = {
        authenticate(context)
    },
    modifier = Modifier.padding(8.dp)
) {
    Text("Аутентификация", fontSize = 22.sp)
}

Прежде всего кнопка доступна, если только доступна биометрия. И в этом случае пользователь может нажать на кнопку, и в этом случае будет выполняться функция authenticate():

fun authenticate(context: FragmentActivity) {

    val executor = context.mainExecutor
    val biometricPrompt = BiometricPrompt(
        context,
        executor,
        object : BiometricPrompt.AuthenticationCallback() {

            override fun onAuthenticationSucceeded(
                result: BiometricPrompt.AuthenticationResult) {
                Toast.makeText(context, "Аутентифкация пройдена", Toast.LENGTH_LONG).show()
            }

            override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                Toast.makeText(context, "Ошибка при аутентификации: $errString", Toast.LENGTH_LONG).show()
            }

            override fun onAuthenticationFailed() {
                Toast.makeText(context, "Не удалось пройти аутентификацию", Toast.LENGTH_LONG).show()
            }
        })

Сначала определяется объект BiometricPrompt, который настраивает диалоговое окно биометрического запроса и определяет набор методов обратного вызова аутентификации, которые можно вызывать для уведомления приложения об успехе или неудаче процесса аутентификации:

  • onAuthenticationSucceeded(): вызывается при успешной аутентификации

  • onAuthenticationError(): вызывается, если в процессе аутентификации произойдет ошибка

  • onAuthenticationFailed(): вызывается, если пользователю не удалось пройти аутентификацию

Эти методы необходимо обернуть в объект класса BiometricPrompt.AuthenticationCallback.

И в конце собственно создается окно запроса:

val promptInfo = BiometricPrompt.PromptInfo.Builder()
    .setTitle("Биометрическая аутентификация")
    .setDescription("Используйте отпечаток пальца или камеру для аутентификации")
    .setNegativeButtonText("Отмена")
    .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
    .build()

biometricPrompt.authenticate(promptInfo)

Класс BiometricPrompt.PromptInfo.Builder создает новый экземпляр PromptInfo, настроенный с заголовком, подзаголовком и текстом описания, которые будут отображаться в диалоговом окне. Наконец, вызывается метод authenticate() экземпляра BiometricPrompt, которому передается объект PromptInfo.

В итоге при запуска приложения нам отобразится кнопка, по нажатию на которую отобразится диалоговое окно для ввода отпечатка пальца или сканирования лица. И нам надо будет приложенить палец для сканирования отпечатка, и при успешной аутентификации мы увидим соответствующее сообщение:

Биометрическая аутентификация в приложении Android на Jetpack Compose и Kotlin
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850