Android 使用设备凭据时 AuthenticationCallback 中的生物识别身份验证无效变量
Android biometric authentication invalid variables in AuthenticationCallback when using device credential
我正在使用 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 和文本无效! autneticate
调用onAuthenticationSucceeded时函数输入(数据)不一样!
(如果您尝试对其进行测试,请务必使用 DeviceCredentials 而不是生物识别技术,我指的是图案或密码,取消设置您的指纹)
为什么数据在回调中无效?
它在 android 10 或指纹
上工作正常
我不想使用 onSaveInstanceState。
所以这看起来很奇怪,但我设法通过向 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)
由于您询问的是 setDeviceCredentialAllowed(true)
,可以肯定的是您没有关注 the recommended implementation that uses CryptoObject
. (Also check out this 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()
.
中的错误代码
P.S。
您应该将 private val TAG = MainActivity::class.java.name
替换为 private val TAG = "MainActivity"
.
当您创建 BiometricPrompt
class 的新实例时,它会向 activity 添加一个 LifecycleObserver
并且我发现它永远不会删除它。因此,当您在 activity 中有多个 BiometricPrompt
实例时,同时有多个 LifecycleObserver
会导致此问题。
对于 Android Q 之前的设备,有一个名为 DeviceCredentialHandlerActivity
的透明 activity 和一个名为 DeviceCredentialHandlerBridge
的网桥 class 支持设备凭据身份验证. BiometricPrompt
管理不同状态的网桥,如果需要,最终在 onResume
状态(离开凭证 window 后返回 activity 时)调用回调方法。当有多个LifecycleObserver
时,第一个会处理结果并重置桥,因此其他观察者无事可做。这就是第一个回调实现在您的代码中调用两次的原因。
解法:
当您创建 BiometricPrompt
class 的新实例时,您应该从 activity 中删除 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
)
我正在使用 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 和文本无效! autneticate
调用onAuthenticationSucceeded时函数输入(数据)不一样!
(如果您尝试对其进行测试,请务必使用 DeviceCredentials 而不是生物识别技术,我指的是图案或密码,取消设置您的指纹) 为什么数据在回调中无效?
它在 android 10 或指纹
上工作正常我不想使用 onSaveInstanceState。
所以这看起来很奇怪,但我设法通过向 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)
由于您询问的是 setDeviceCredentialAllowed(true)
,可以肯定的是您没有关注 the recommended implementation that uses CryptoObject
. (Also check out this 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()
.
P.S。
您应该将 private val TAG = MainActivity::class.java.name
替换为 private val TAG = "MainActivity"
.
当您创建 BiometricPrompt
class 的新实例时,它会向 activity 添加一个 LifecycleObserver
并且我发现它永远不会删除它。因此,当您在 activity 中有多个 BiometricPrompt
实例时,同时有多个 LifecycleObserver
会导致此问题。
对于 Android Q 之前的设备,有一个名为 DeviceCredentialHandlerActivity
的透明 activity 和一个名为 DeviceCredentialHandlerBridge
的网桥 class 支持设备凭据身份验证. BiometricPrompt
管理不同状态的网桥,如果需要,最终在 onResume
状态(离开凭证 window 后返回 activity 时)调用回调方法。当有多个LifecycleObserver
时,第一个会处理结果并重置桥,因此其他观察者无事可做。这就是第一个回调实现在您的代码中调用两次的原因。
解法:
当您创建 BiometricPrompt
class 的新实例时,您应该从 activity 中删除 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
)