首页 > 解决方案 > 无法使用 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,更新apiTokenandgoogleToken变量,但是在下面的代码中,我想在请求失败时更新apiTokenand 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()

标签: androidkotlingoogle-apigoogle-oauthokhttp

解决方案


经过一番挖掘,我找到了答案。为了类似船上的其他人,我将在下面记录问题和解决方案:

当我开始遇到与 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 ),我建议将其用于新项目,但由于期限紧迫,我无法做到在这个版本中。


推荐阅读