在 Android Retrofit 中并行调用多个 API 时,身份验证器刷新令牌

fre*_*yle 5 android android-authenticator retrofit okhttp refresh-token

我想在访问令牌过期时刷新它。我已经实现了 Authenticator,如下所示:

@Singleton
class TokenAuthenticator(
    val authService: Lazy<AuthService>,
    private val sharedPreferences: SharedPreferences
) : Authenticator {


override fun authenticate(route: Route?, response: Response): Request? {
    return authRequestWithNewToken(response.request)
}

private fun authRequestWithNewToken(
    request: Request
): Request? {
    getFreshAccessToken()?.let { freshToken ->
        return getNewRequest(request, freshToken)
    }
    return null
}

private fun getFreshAccessToken(): String? {
    val refreshResponse = authService.get().refreshTokenTemp().execute()
    if (refreshResponse.isSuccessful) {
        val updatedAccessToken =
            refreshResponse.body()?.data?.accessToken ?: return null
        val updatedRefreshToken =
            refreshResponse.body()?.data?.refreshToken ?: return null
        updateToken(updatedAccessToken, updatedRefreshToken)
        Timber.tag("TOKEN")
            .d("Auth Updated token\n AccessToken: $updatedAccessToken \n RefreshToken: $updatedRefreshToken")
        return updatedAccessToken
    } else {
        return null
    }
}

private fun getNewRequest(request: Request, accessToken: String): Request {
    return request.newBuilder()
        .header(
            AppConstants.PARAMS.AUTH,
            AppConstants.PARAMS.BEARER + accessToken
        )
        .header("Accept", "application/json")
        .header("User-Agent", AppConstants.PARAMS.USER_AGENT)
        .build()
}

private fun updateToken(accessToken: String, refreshToken: String) {
    with(sharedPreferences.edit()) {
        putString(AppConstants.SHAREDPREFERENCE.ACCESS_TOKEN, accessToken)
        putString(AppConstants.SHAREDPREFERENCE.REFRESH_TOKEN, refreshToken).apply()
    }
    AppVariables.SessionData.accessToken = accessToken
    AppVariables.SessionData.refreshToken = refreshToken
    Timber.tag("TOKEN").d("Token Refreshed")
}
}
Run Code Online (Sandbox Code Playgroud)

即使使用此功能后,由于发生并行 API 调用,我的应用程序仍然超时,并且用户被带到登录屏幕。

即使花了很多时间我也无法弄清楚出了什么问题。

我看到人们在尝试@synchronised,我也尝试实施,但没有成功。

最后我看到他添加了一个这样的调度程序:

    @Singleton
    @Provides
    fun provideOkhttpClient(tokenAuthenticator: TokenAuthenticator): OkHttpClient {
     //***********like this**********************//
        val dispatcher = Dispatcher()
        dispatcher.maxRequests = 1
     //******************************************//
        return OkHttpClient.Builder()
            .dispatcher(dispatcher)
            .connectTimeout(180, TimeUnit.SECONDS)
            .readTimeout(180, TimeUnit.SECONDS)
            .writeTimeout(180, TimeUnit.SECONDS)
            .authenticator(tokenAuthenticator)
            .addInterceptor(TokenInterceptor())
            .build()
    }
Run Code Online (Sandbox Code Playgroud)

这似乎解决了我的问题。但是,我不知道这意味着什么。是否建议在生产中使用?

我可以在文档中看到以下内容:

并发执行的最大请求数。此请求在内存中排队,等待正在运行的调用完成。如果在调用此函数时有超过 maxRequests 个请求正在处理中,则这些请求将保持在处理中。

我是否错过了一些可能会在以后咬我屁股的东西?

其他参考:link1 link2

Gar*_*her 0

要求是同步刷新尝试,以确保来自视图模型的所有正在进行的 API 请求获得相同的(旋转的)刷新令牌响应。因此,一个视图模型发出请求,而所有其他视图模型则在工作线程上等待。

一种方法是像我的这段代码一样对延续进行排队。这使您能够编写这样代码,仅发送一次 HTTP 请求。在你的例子中,OkHttp 调度程序似乎在做同样的工作,所以感觉走在正确的轨道上。

override suspend fun refreshAccessToken(): String {
    return this.concurrencyHandler.execute(this::performRefreshTokenGrant)
}
Run Code Online (Sandbox Code Playgroud)

为了确保行为正确,您应该测试到期事件。首先,我将跟踪 HTTP(S) 流量并验证刷新请求是否仅触发一次。还要测试多个刷新请求是否可以相继运行。通过本地开发选项向访问令牌添加字符以使其表现为过期,模拟访问令牌过期非常容易。您可以运行我的代码示例来查看该方法。