android - okhttp 身份验证器请求重试不起作用

发布于 2025-01-13 00:27:56 字数 2881 浏览 4 评论 0原文

您好,我们正在身份验证系统中使用 okhttp/retrofitoauth2,实际上,当 api 端点返回 UNAUTHORIZED 时,会触发 Authenticator,我们成功获取新凭据,但问题是身份验证器没有重试先前返回 UNAUTHORIZED 响应的端点, 这就是我所做的:

@Singleton
class RefreshTokenAuthenticator @Inject constructor(
    @Named(CURRENT_CREDENTIALS_PREFERENCES_NAME)
    private val userDataPreferences: SharedPreferences
) : okhttp3.Authenticator {

    var apiHolder: ApiHolder? = null
    private val json = Json { ignoreUnknownKeys = true }

    override fun authenticate(route: Route?, response: Response): Request? {
        val credentialsString = userDataPreferences.getString(
            USER_CREDENTIALS_KEY, USER_CREDENTIALS_KEY
        ) ?: ""
        var request: Request? = null
        val credentials: UserCredentials? = try {
            json.decodeFromString(
                UserCredentials.serializer(), credentialsString
            )
        } catch (e: SerializationException) {
            null
        }

        credentials?.let { userCredentials ->
            CoroutineScope(Dispatchers.IO).launch {
                try {
                    val refreshTokenResponse = apiHolder?.api?.refreshToken(userCredentials)
                    if (refreshTokenResponse?.isSuccessful == true) {
                        val responseBody = refreshTokenResponse.body()?.data
                        responseBody?.let { body ->
                            val userCredentialsJson = json.encodeToString(
                                UserCredentials.serializer(),
                                body
                            )
                            userDataPreferences.edit().putString(
                                USER_CREDENTIALS_KEY,
                                userCredentialsJson
                            ).apply()

                            request = response.request
                                .newBuilder()
                                .header("Authorization", "Bearer ${body.accessToken}")
                                .build()

                        }
                    } else {
                        request = null
                        //TODO: handle errors
                    }
                } catch (e: Exception) {
                    Timber.e(e)
                }
            }
        }

        return request
    }
}

这就是我创建 OkhttpClient 的方式:

////
return OkHttpClient.Builder()
            .addInterceptor(defaultHeadersInterceptor)
            .addInterceptor(authorizationInterceptor)
            .addInterceptor(errorHandlingInterceptor)
            .addInterceptor(loggingInterceptor)
            .addInterceptor(CurlInterceptor {
                Timber.d("Ok2Curl $it ")
            })
            .authenticator(refreshTokenAuthenticator)
            .build()

注意:如果需要,授权拦截器会在每个请求中添加承载令牌。

Hello we're working with okhttp/retrofit and oauth2 in the authentication system, actually the Authenticator is triggered when an api endpoint returns UNAUTHORIZED and we get the new credentials successfully, but the problem is the authenticator not retrying the previously endpoint that returned UNAUTHORIZED in response,
this is what i did:

@Singleton
class RefreshTokenAuthenticator @Inject constructor(
    @Named(CURRENT_CREDENTIALS_PREFERENCES_NAME)
    private val userDataPreferences: SharedPreferences
) : okhttp3.Authenticator {

    var apiHolder: ApiHolder? = null
    private val json = Json { ignoreUnknownKeys = true }

    override fun authenticate(route: Route?, response: Response): Request? {
        val credentialsString = userDataPreferences.getString(
            USER_CREDENTIALS_KEY, USER_CREDENTIALS_KEY
        ) ?: ""
        var request: Request? = null
        val credentials: UserCredentials? = try {
            json.decodeFromString(
                UserCredentials.serializer(), credentialsString
            )
        } catch (e: SerializationException) {
            null
        }

        credentials?.let { userCredentials ->
            CoroutineScope(Dispatchers.IO).launch {
                try {
                    val refreshTokenResponse = apiHolder?.api?.refreshToken(userCredentials)
                    if (refreshTokenResponse?.isSuccessful == true) {
                        val responseBody = refreshTokenResponse.body()?.data
                        responseBody?.let { body ->
                            val userCredentialsJson = json.encodeToString(
                                UserCredentials.serializer(),
                                body
                            )
                            userDataPreferences.edit().putString(
                                USER_CREDENTIALS_KEY,
                                userCredentialsJson
                            ).apply()

                            request = response.request
                                .newBuilder()
                                .header("Authorization", "Bearer ${body.accessToken}")
                                .build()

                        }
                    } else {
                        request = null
                        //TODO: handle errors
                    }
                } catch (e: Exception) {
                    Timber.e(e)
                }
            }
        }

        return request
    }
}

and this is how i create the OkhttpClient:

////
return OkHttpClient.Builder()
            .addInterceptor(defaultHeadersInterceptor)
            .addInterceptor(authorizationInterceptor)
            .addInterceptor(errorHandlingInterceptor)
            .addInterceptor(loggingInterceptor)
            .addInterceptor(CurlInterceptor {
                Timber.d("Ok2Curl $it ")
            })
            .authenticator(refreshTokenAuthenticator)
            .build()

NB: the authorization Interceptor adds the Bearer token in every request if needed.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

梦亿 2025-01-20 00:27:56

看起来您需要分解 API 调用和重试代码,并且拦截器机制可能不会执行您想要的操作。

我的 API Client 显示了一种替代方法,并包装了 okhttp,以便 Android 视图模型通过单行程序进行 API 调用,而不是使用 okhttp 直接地。

Looks like you need to factor out the API calling and retrying code, and that the interceptor mechanism may not do what you want.

My API Client shows an alternative approach, and wraps okhttp, so that Android view models make API calls via a one liner, rather than using okhttp directly.

爱的十字路口 2025-01-20 00:27:56

正如加里·阿彻(Gary Archer)提到的,你可以做些什么来完成未经授权的请求的重试策略,只需添加另一个拦截器,就像你已经对之前的拦截器所做的那样,并让它们在一段时间后重复你的调用 - 这完全取决于你做什么重试策略看起来像这样,但我可以给你一个可以做什么的小例子:

class RetryOnUnauthorizedInterceptor @Inject constructor(
    @DispatcherDefault private val dispatcher: CoroutineDispatcher
) : Interceptor {

    constructor() : this(Dispatchers.Default)

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val response = chain.proceed(request)
        val responseCode = response.code
        return if (responseCode == HTTP_UNAUTHORIZED) {
            retry(chain, request, response)
        } else response
    }

    private fun retry(chain: Interceptor.Chain, request: Request, previousResponse: Response): Response {
        var response = previousResponse
        var retryCount = 0
        val responseCodes = mutableListOf(response.code)
        do {
            response.close()
            runBlocking(dispatcher) {
                delay(RETRY_DELAY_MILLIS)
            }
            response = chain.proceed(request)
            responseCodes.add(response.code)
            retryCount++
        } while (response.code == HTTP_UNAUTHORIZED && retryCount < MAX_RETRY_COUNT)
        return response
    }

    companion object {
        private const val RETRY_DELAY_MILLIS = 2000L
        private const val MAX_RETRY_COUNT = 2
        private const val HTTP_UNAUTHORIZED = 401
    }
}

这里是使用协程完成的,但你可以将其调整为你的异步实现。

快乐编码!

As Gary Archer did mention, what you can do to accomplish the retry policy for your unathorized requests, just add another Interceptor as you have done with the previous ones already and make them repeat your call after a while - it is totally up to you what the retry policy will look like, but I can give you an small example of what can be done:

class RetryOnUnauthorizedInterceptor @Inject constructor(
    @DispatcherDefault private val dispatcher: CoroutineDispatcher
) : Interceptor {

    constructor() : this(Dispatchers.Default)

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val response = chain.proceed(request)
        val responseCode = response.code
        return if (responseCode == HTTP_UNAUTHORIZED) {
            retry(chain, request, response)
        } else response
    }

    private fun retry(chain: Interceptor.Chain, request: Request, previousResponse: Response): Response {
        var response = previousResponse
        var retryCount = 0
        val responseCodes = mutableListOf(response.code)
        do {
            response.close()
            runBlocking(dispatcher) {
                delay(RETRY_DELAY_MILLIS)
            }
            response = chain.proceed(request)
            responseCodes.add(response.code)
            retryCount++
        } while (response.code == HTTP_UNAUTHORIZED && retryCount < MAX_RETRY_COUNT)
        return response
    }

    companion object {
        private const val RETRY_DELAY_MILLIS = 2000L
        private const val MAX_RETRY_COUNT = 2
        private const val HTTP_UNAUTHORIZED = 401
    }
}

This one here is done with the Coroutines, but you can shape it to your async implementation.

Happy coding!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文