【问题标题】:Android biometric authentication invalid variables in AuthenticationCallback when using device credential使用设备凭据时,AuthenticationCallback 中的 Android 生物识别身份验证无效变量
【发布时间】:2020-01-08 15:17:10
【问题描述】:

我正在使用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 logcat是这样的:

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 和 text 无效! autneticate调用onAuthenticationSucceeded时函数input(data)不一样!

(如果您尝试对其进行测试,请务必使用 DeviceCredentials 而不是生物识别,我的意思是图案或密码,请取消设置您的指纹) 为什么callBack中的数据无效?

在 android 10 上运行正常或使用指纹

我不想使用 onSaveInstanceState。

【问题讨论】:

  • 您是否尝试创建自定义BiometricPrompt.AuthenticationCallback 并创建您的类并将您的参数传递给构造函数?或者可能将MyData 参数保留为活动参数并访问该参数?
  • 您在哪个 Android 版本上进行测试?与早期的 Android 版本相比,androidx 生物识别库在 Android Q 上的作用略有不同。
  • @michael AVD Android P (9.0)
  • 但我认为这个问题甚至与Android无关!它可以是任何回调我错过了什么吗?
  • 我认为它与android生命周期有关

标签: android kotlin android-biometric-prompt android-biometric


【解决方案1】:

当您创建BiometricPrompt 类的新实例时,它会在活动中添加LifecycleObserver,并且我发现它永远不会删除它。因此,当您在一个活动中有多个BiometricPrompt 实例时,同时有多个LifecycleObserver 会导致此问题。

对于 Android Q 之前的设备,有一个名为 DeviceCredentialHandlerActivity 的透明 Activity 和一个名为 DeviceCredentialHandlerBridge 的桥接类,它们支持设备凭据身份验证。 BiometricPrompt 管理不同状态的网桥,最后在需要时调用onResume 状态(离开凭证窗口后返回活动时)的回调方法。当有多个LifecycleObserver时,第一个会处理结果并重置网桥,所以其他观察者无事可做。这就是第一个回调实现在您的代码中调用两次的原因。

解决方案: 当您创建 BiometricPrompt 类的新实例时,您应该从活动中删除 LifecycleObserver。由于无法直接访问观察者,因此您需要在此处使用反射。我根据此解决方案修改了您的代码,如下所示:

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
)

【讨论】:

  • 您是否有运气在片段上实现此修复?我试过 fragment.lifecycle.removeObserver(it) 和 fragment.activity.lifecycle.removeObserver(it),但似乎都没有解决问题。
【解决方案2】:

所以这看起来很奇怪,但我设法通过向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)

【讨论】:

  • 感谢您的回答。我想找到一个好的解决方案,因为我们可以在android的很多地方保存数据。
【解决方案3】:

由于您询问的是setDeviceCredentialAllowed(true),因此可以肯定地假设您没有关注使用CryptoObjectthe recommended implementation。 (也可以看看这个blog post。)

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() 中的错误代码。

附: 您应该将private val TAG = MainActivity::class.java.name 替换为private val TAG = "MainActivity"

【讨论】:

  • 我的目标是 API 28。我没有问题。问题是为什么会发生这种情况。
  • 你的 targetSdkVersion 是 28 岁?你的minSdkVersion 是什么?
  • 最低 API 21。
猜你喜欢
  • 1970-01-01
  • 2017-09-23
  • 2022-06-11
  • 2020-11-25
  • 2019-03-22
  • 1970-01-01
  • 2015-12-20
  • 2014-10-15
  • 1970-01-01
相关资源
最近更新 更多