无法使用 GoogleSignInClient.silentSignIn() 在 OkHttpClient 身份验证器中检索 ID 令牌
Cannot use GoogleSignInClient.silentSignIn() to retrieve an id token in an OkHttpClient Authenticator
在我用 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)
,因为我使用 OnCompleteListener
来更新 UI,更新 apiToken
和 googleToken
变量,但是在下面的代码中,我想在请求失败时更新 apiToken
和 googleToken
,而不需要我项目中的其他代码将请求重新发送到后端 API .
TLDR:我想在 OkHttpClient 身份验证器中调用 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 Billing Library 以类似方式锁定的类似错误时,我的怀疑就增加了。一项调查显示我是从 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),我建议将其用于新项目,但由于截止日期紧迫,我未能在此版本中这样做。
在我用 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)
,因为我使用 OnCompleteListener
来更新 UI,更新 apiToken
和 googleToken
变量,但是在下面的代码中,我想在请求失败时更新 apiToken
和 googleToken
,而不需要我项目中的其他代码将请求重新发送到后端 API .
TLDR:我想在 OkHttpClient 身份验证器中调用 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 Billing Library 以类似方式锁定的类似错误时,我的怀疑就增加了。一项调查显示我是从 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),我建议将其用于新项目,但由于截止日期紧迫,我未能在此版本中这样做。