Недопустимые переменные биометрической аутентификации Android в AuthenticationCallback при использовании учетных данных устройства

Я использую androidx.biometric:biometric:1.0.1, все работает нормально, но когда у меня есть устройство без биометрического датчика (или когда пользователь не установил свой отпечаток пальца и т. Д.), Я пытаюсь использовать DeviceCredentials после выполнения аутентификации мои входные данные функции недействительны.

class MainActivity : AppCompatActivity() {

    private val TAG = MainActivity::class.java.name

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<View>(R.id.first).setOnClickListener {
            authenticate(MyData(1, "first"))
        }

        findViewById<View>(R.id.second).setOnClickListener {
            authenticate(MyData(2, "second"))
        }
    }

    private fun authenticate(data: MyData) {
        Log.e(TAG, "starting auth with $data")
        val biometricPrompt = BiometricPrompt(
            this,
            ContextCompat.getMainExecutor(this),
            object : BiometricPrompt.AuthenticationCallback() {
                override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                    Log.e(TAG, "auth done : $data")
                }
            })

        val promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setDeviceCredentialAllowed(true)
            .setTitle("title")
            .build()
        biometricPrompt.authenticate(promptInfo)
    }
}

data class MyData(
    val id: Int,
    val text: String
)

Сначала я нажимаю кнопку first, проверяю подлинность, затем нажимаю кнопку second и проверяю подлинность, затем логарифм Android выглядит следующим образом:

E/com.test.biometrictest.MainActivity: starting auth with MyData(id=1, text=first)
E/com.test.biometrictest.MainActivity: auth done : MyData(id=1, text=first)
E/com.test.biometrictest.MainActivity: starting auth with MyData(id=2, text=second)
E/com.test.biometrictest.MainActivity: auth done : MyData(id=1, text=first)

как вы видите в последней строке MyData id и текст недействительны! autneticate функция input (data) не то же самое, когда вызывается onAuthenticationSucceeded!

(если вы попытаетесь протестировать его, убедитесь, что используете DeviceCredentials, а не биометрию, я имею в виду шаблон или пароль, снимите отпечаток пальца) Почему данные не действительны в callBack?

нормально работает на android 10 или с отпечатком пальца

Я не хочу использовать onSaveInstanceState.


person feridok    schedule 05.01.2020    source источник
comment
вы пытались создать собственный BiometricPrompt.AuthenticationCallback и вместо этого создать свой класс и передать свой параметр в конструктор? или, может быть, оставить параметр MyData как параметр действия и вместо этого получить доступ к этому параметру?   -  person seyed Jafari    schedule 07.01.2020
comment
На какой версии Android вы тестируете? Биометрическая библиотека androidx работает на Android Q несколько иначе, чем в более ранних версиях Android.   -  person Michael    schedule 07.01.2020
comment
@michael AVD Android P (9.0)   -  person feridok    schedule 07.01.2020
comment
Но я не думаю, что вопрос касается даже Android! это может быть любой обратный вызов. Я что-то упускаю?   -  person seyed Jafari    schedule 07.01.2020
comment
Я думаю, это как-то связано с жизненным циклом Android   -  person feridok    schedule 07.01.2020
comment
Хммм ... Я только что скопировал ваш полный код, и он работал на Google Pixel 2 (Android 10). Я использовал контактный ввод вместо датчика отпечатков пальцев.   -  person Vall0n    schedule 07.01.2020
comment
не использовать API ‹29 это нормально на Android 10   -  person feridok    schedule 07.01.2020
comment
Я думаю, что эта проблема возникает из-за FragmentManager и того, как эта библиотека обрабатывает класс Callback.   -  person seyed Jafari    schedule 07.01.2020
comment
Вы пытались переместить свой biometricPrompt из Authenticate () и преобразовать его в поле действия, чтобы можно было повторно использовать тот же экземпляр?   -  person Venator85    schedule 12.01.2020


Ответы (3)


Когда вы создаете новый экземпляр класса BiometricPrompt, он добавляет LifecycleObserver к активности и, как я понял, никогда не удаляет его. Поэтому, когда у вас есть несколько экземпляров BiometricPrompt в действии, существует несколько LifecycleObserver одновременно, которые вызывают эту проблему.

Для устройств до Android Q существует прозрачное действие с именем DeviceCredentialHandlerActivity и класс моста с именем DeviceCredentialHandlerBridge, которые поддерживают аутентификацию учетных данных устройства. BiometricPrompt управляет мостом в разных состояниях и, наконец, вызывает методы обратного вызова в состоянии onResume (при возврате к активности после выхода из окна учетных данных), если это необходимо. Когда их несколько LifecycleObserver, первый обработает результат и сбросит мост, поэтому другим наблюдателям нечего делать. Это причина того, что первая реализация обратного вызова вызывает в вашем коде дважды.

Решение. Удалите LifecycleObserver из активности при создании нового экземпляра класса BiometricPrompt. Поскольку прямого доступа к наблюдателю нет, вам нужно использовать здесь отражение. Я изменил ваш код на основе этого решения, как показано ниже:

class MainActivity : AppCompatActivity() {

    private val TAG = MainActivity::class.java.name
    private var lastLifecycleObserver: LifecycleObserver? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<View>(R.id.first).setOnClickListener {
            authenticate(MyData(1, "first"))
        }

        findViewById<View>(R.id.second).setOnClickListener {
            authenticate(MyData(2, "second"))
        }
    }

    private fun authenticate(data: MyData) {
        Log.e(TAG, "starting auth with $data")
        lastLifecycleObserver?.let {
            lifecycle.removeObserver(it)
            lastLifecycleObserver = null
        }
        val biometricPrompt = BiometricPrompt(
                this,
                ContextCompat.getMainExecutor(this),
                object : BiometricPrompt.AuthenticationCallback() {
                    override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                        Log.e(TAG, "auth done : $data")
                    }
                })

        var field = BiometricPrompt::class.java.getDeclaredField("mLifecycleObserver")
        field.isAccessible = true
        lastLifecycleObserver = field.get(biometricPrompt) as LifecycleObserver

        val promptInfo = BiometricPrompt.PromptInfo.Builder()
                .setDeviceCredentialAllowed(true)
                .setTitle("title")
                .build()
        biometricPrompt.authenticate(promptInfo)
    }
}

data class MyData(
        val id: Int,
        val text: String
)
person Mir Milad Hosseiny    schedule 07.01.2020
comment
Удалось ли вам добиться этого исправления на фрагменте? Я пробовал fragment.lifecycle.removeObserver (it) и fragment.activity.lifecycle.removeObserver (it), но, похоже, ни один из них не решил проблему. - person saltandpepper; 27.03.2020

Это кажется странным, но мне удалось заставить его работать, введя параметр в MainActivity

вот рабочий код:

class MainActivity : AppCompatActivity() {

    var dataParam : MyData? = null

    companion object {
        private val TAG = MainActivity::class.java.name

    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<View>(R.id.firstBtn).setOnClickListener {
            authenticate(MyData(1, "first"))
        }

        findViewById<View>(R.id.secondBtn).setOnClickListener {
            authenticate(MyData(2, "second"))
        }
    }


    private fun authenticate(data: MyData) {
        Log.e(TAG, "starting auth with $data")
        dataParam = data
        val biometricPrompt = BiometricPrompt(
                this,
                ContextCompat.getMainExecutor(this),
                object : BiometricPrompt.AuthenticationCallback() {
                    override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                        Log.e(TAG, "auth done : $dataParam")
                    }
                })

        val promptInfo = BiometricPrompt.PromptInfo.Builder()
                .setDeviceCredentialAllowed(true)
                .setTitle("title")
                .build()
        biometricPrompt.authenticate(promptInfo)
    }
}

data class MyData(
        val id: Int,
        val text: String
)

Теперь вывод:

E/com.worldsnas.bioissuesample.MainActivity: starting auth with MyData(id=1, text=first)
E/com.worldsnas.bioissuesample.MainActivity: auth done : MyData(id=1, text=first)
E/com.worldsnas.bioissuesample.MainActivity: starting auth with MyData(id=2, text=second)
E/com.worldsnas.bioissuesample.MainActivity: auth done : MyData(id=2, text=second)
person seyed Jafari    schedule 07.01.2020
comment
спасибо за ваш ответ. Я хочу найти хорошее решение, потому что в Android мы можем сохранять данные во многих местах. - person feridok; 08.01.2020

Поскольку вы спрашиваете о setDeviceCredentialAllowed(true), можно с уверенностью предположить, что вы не подписаны на рекомендуемая реализация, в которой используется CryptoObject. (Также ознакомьтесь с этим сообщением в блоге < / а>.)

Функциональность setDeviceCredentialAllowed(true) будет работать только с API 21+, но у вас есть несколько вариантов для ее обработки в вашем приложении в зависимости от вашей minSdkVersion.

API 23+

если ваше приложение нацелено на API 23+, вы можете сделать

 if (keyguardManager.isDeviceSecure()){
     biometricPrompt.authenticate(promptInfo)
 }

от API 16 до API 23 до версии

Если ваше приложение должно выполнить проверку до API 23, вы можете использовать

if (keyguardManager.isKeyguardSecure) {
   biometricPrompt.authenticate(promptInfo)
}

KeyguardManager.isKeyguardSecure() эквивалентно isDeviceSecure(), если устройство не заблокировано SIM-картой.

API 14 - API 16

Если вы ориентируетесь на уровень ниже API 16 или блокировка SIM-карты является проблемой, вам следует просто полагаться на коды ошибок в обратном вызове onAuthenticationError().

P.S. Вам следует заменить private val TAG = MainActivity::class.java.name на private val TAG = "MainActivity".

person Isai Damier    schedule 07.01.2020
comment
Я нацелен на API 28. У меня нет проблем. вопрос в том, почему это происходит. - person feridok; 08.01.2020
comment
вашему targetSdkVersion 28 лет? Какой у вас minSdkVersion? - person Isai Damier; 08.01.2020
comment
минимум API 21. - person feridok; 09.01.2020