android - 无法使用 GoogleSignInClient.silentSignIn() 在 OkHttpClient Authenticator 中检索 id 令牌
问题描述
在我用 Kotlin 编写的 Android 应用程序中,我使用 Google 登录进行授权,并使用 JWT 令牌通过我的后端 API 进行身份验证。我的 JWT 令牌和 Google Sign-In 的 JWT 令牌都会在 1 小时后过期。
当对我的后端 API 的请求失败并出现 401 错误时,我希望我的应用程序使用 Google 登录重新授权并使用我的 API 重新进行身份验证,因此我向 OkHttpClient 添加了一个身份验证器。
但是,当我运行下面的代码时,silentSignIn 任务会永久锁定并且不会在线响应val result = Tasks.await(task)
。
我错过了一些明显的东西吗?我找不到任何理由说明为什么当从身份验证器调用silentSignIn() 时会锁定。
在其他地方,我不使用Tasks.await(task)
,因为我使用 anOnCompleteListener
来更新 UI,更新apiToken
andgoogleToken
变量,但是在下面的代码中,我想在请求失败时更新apiToken
and googleToken
,而不需要我项目中的其他代码重新启动对后端 API 的请求。
TLDR:我想在 OkHttpClient Authenticator 中调用 GoogleSignIn.silentSignIn(),获取令牌,然后立即重试请求,但我的应用程序在 Tasks.await 上冻结。
val RC_SIGN_IN = 9001
var apiToken: String? = null
var googleToken: String? = null
val client: OkHttpClient = OkHttpClient
.Builder()
.authenticator(object : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
return if (response.code == 401) {
// Configure sign-in to request the user's ID, email address, and basic
// profile. ID and basic profile are included in DEFAULT_SIGN_IN.
val gso =
GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.requestIdToken("token_goes_here")
.build()
// Build a GoogleApiClient with access to the Google Sign-In API and the
// options specified by gso.
val googleSignInClient = GoogleSignIn.getClient(this@MainActivity, gso)
val task = googleSignInClient.silentSignIn().addOnCompleteListener {
val result = it.getResult(ApiException::class.java)
googleToken = result?.idToken
}.addOnFailureListener {
val signInIntent = googleSignInClient.signInIntent
startActivityForResult(signInIntent, RC_SIGN_IN)
}
if (task.isSuccessful) {
googleToken = task.result?.idToken
} else {
try {
/**** The following line is where the routine locks up: ****/
val result = Tasks.await(task)
googleToken = result?.idToken
} catch (ex: ExecutionException) {
// task failed
Log.e(TAG, "authenticate error:{ex}")
} catch (ex: InterruptedException) {
// an interrupt occurred while waiting for the task to finish
Log.e(TAG, "authenticate error:{ex}")
}
}
apiToken = getApiToken(googleToken)
response.request
.newBuilder()
.header("Authorization", "Bearer $apiToken")
.build()
} else {
null
}
}
}
)
.build()
解决方案
经过一番挖掘,我找到了答案。为了类似船上的其他人,我将在下面记录问题和解决方案:
当我开始遇到与 Google Play 计费库以类似方式锁定的类似错误时,我的怀疑越来越大。一项调查显示我正在从 runBlocking { } 上下文调用我的 API get() 代码。Google 使用了与 OkHTTP 相同的线程,因此 runBlocking 导致 OkHTTP 之外的所有网络 I/O 都被阻塞,直到 OkHTTP 完成。
解决方案是尽可能删除 runBlocking,通过 launch {} 异步加载数据,并在完成后使用 notifyDataSetChanged() 更新 RecyclerView。我无法完全删除 runBlocking,但它现在使用得很少,我已经重新设计了 Task.await() 块,使其超时,并调用超时意图:
try {
val result = Tasks.await(task, 5, TimeUnit.SECONDS)
googleToken = result?.idToken
} catch (ex: ExecutionException) {
// task failed
Log.e(TAG, "authenticate error:{ex}")
val signInIntent = googleSignInClient.signInIntent
startActivityForResult(signInIntent, RC_SIGN_IN)
} catch (ex: InterruptedException) {
// an interrupt occurred while waiting for the task to finish
Log.e(TAG, "authenticate error:{ex}")
val signInIntent = googleSignInClient.signInIntent
startActivityForResult(signInIntent, RC_SIGN_IN)
} catch (ex: TimeoutException) {
// an timeout occurred while waiting for the task to finish
Log.e(TAG, "authenticate error:{ex}")
val signInIntent = googleSignInClient.signInIntent
startActivityForResult(signInIntent, RC_SIGN_IN)
}
一个更好的解决方案是实现 ViewModels ( https://developer.android.com/topic/libraries/architecture/viewmodel ),我建议将其用于新项目,但由于期限紧迫,我无法做到在这个版本中。
推荐阅读
- php - Laravel,WAMP,无法在不同的电脑上访问路由
- scala - Scala 类型约束检查参数值
- ios - Firebase 未在 iOS 应用中触发推送通知
- typescript - BlinkID 插件错误 Ionic 4 - Native:尝试调用 BlinkId.BarcodeRecognizer,但未安装 BlinkId 插件
- javascript - RxJS:创建和订阅可观察的 onClick
- java - 查找具有最小最大点的两个边界框之间的距离
- string - valueerror:无法在 python 2.7 中将字符串转换为浮点数
- python - 工作 Azure HTTP 函数应用在发布时中断
- database - Sembast 中的 put 和 add 有什么区别?
- reactjs - 为什么我不能在状态 Reactjs 中保存这个请求