首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Okhttp Authenticator多线程

Okhttp Authenticator多线程
EN

Stack Overflow用户
提问于 2015-09-02 21:08:48
回答 6查看 4.3K关注 0票数 19

我在我的android应用程序中使用了OkHttp,其中有几个异步请求。所有请求都需要与报头一起发送令牌。有时我需要使用RefreshToken刷新令牌,所以我决定使用OkHttpAuthenticator类。

当2个或更多异步请求同时从服务器获得401响应代码时,会发生什么情况?是为每个请求调用验证器的authenticate()方法,还是只为获得401的第一个请求调用一次?

代码语言:javascript
复制
@Override
public Request authenticate(Proxy proxy, Response response) throws IOException
{                
    return null;
}

如何只刷新token一次?

EN

回答 6

Stack Overflow用户

发布于 2020-02-09 04:24:17

API使用单例Authenticator

  • Make确保您用于操作令牌的方法是Synchronized

  • Count重试次数以防止过多的刷新令牌调用

  • 确保用于获取新令牌的调用和用于在本地存储中保存新令牌的本地存储事务不是异步的。或者,如果要使它们异步,请确保在令牌相关的内容被刷新(如果访问令牌已被另一个线程刷新,以避免从后端请求新的访问令牌)后再访问令牌相关内容

这是一个Kotlin语言的示例

代码语言:javascript
复制
@SingleTon
class TokenAuthenticator @Inject constructor(
    private val tokenRepository: TokenRepository
) : Authenticator {
    override fun authenticate(route: Route?, response: Response): Request? {
        return if (isRequestRequiresAuth(response)) {
            val request = response.request()
            authenticateRequestUsingFreshAccessToken(request, retryCount(request) + 1)
        } else {
            null
        }
    }

    private fun retryCount(request: Request): Int =
        request.header("RetryCount")?.toInt() ?: 0

    @Synchronized
    private fun authenticateRequestUsingFreshAccessToken(
        request: Request,
        retryCount: Int
    ): Request? {
        if (retryCount > 2) return null

        tokenRepository.getAccessToken()?.let { lastSavedAccessToken ->
            val accessTokenOfRequest = request.header("Authorization") // Some string manipulation needed here to get the token if you have a Bearer token

            if (accessTokenOfRequest != lastSavedAccessToken) {
                return getNewRequest(request, retryCount, lastSavedAccessToken)
            }
        }

        tokenRepository.getFreshAccessToken()?.let { freshAccessToken ->
            return getNewRequest(request, retryCount, freshAccessToken)
        }

        return null
    }

    private fun getNewRequest(request: Request, retryCount: Int, accessToken: String): Request {
        return request.newBuilder()
            .header("Authorization", "Bearer " + accessToken)
            .header("RetryCount", "$retryCount")
            .build()
    }

    private fun isRequestRequiresAuth(response: Response): Boolean {
        val header = response.request().header("Authorization")
        return header != null && header.startsWith("Bearer ")
    }
}
票数 12
EN

Stack Overflow用户

发布于 2016-10-19 17:43:40

我在这里看到了两个场景,基于你调用的API的工作方式。

第一个绝对更容易处理-调用新的凭证(例如访问令牌)不会使旧的凭证过期。要实现这一点,您可以向凭据添加一个额外的标志,以表明凭据正在被刷新。当您获得401响应时,您将flag设置为true,发出一个请求以获取新的凭据,并且仅当flag等于true时才保存它们,以便只处理第一个响应,其余的将被忽略。确保您对flag的访问是同步的。

另一种情况有点棘手-每次调用新凭据时,服务器端都会将旧凭据设置为过期。为了处理它,我会引入一个新的对象作为semafore它会在每次“凭证被刷新”时被阻塞。为了确保只进行一次“刷新凭证”调用,您需要在与flag同步的代码块中调用它。它可以看起来像这样:

代码语言:javascript
复制
synchronized(stateObject) {
   if(!stateObject.isBeingRefreshed) return;
   Response response = client.execute(request);
   apiClient.setCredentials(response.getNewCredentials());
   stateObject.isBeingRefreshed = false;
}

正如您已经注意到的,有一个额外的检查if(!stateObject.isBeingRefreshed) return;,用于通过以下请求来取消请求新凭据,该请求收到了401响应。

票数 3
EN

Stack Overflow用户

发布于 2018-09-26 16:19:52

这是我的解决方案,以确保在多线程的情况下,使用okhttp3.Authenticator刷新令牌一次

代码语言:javascript
复制
class Reauthenticator : Authenticator {

    override fun authenticate(route: Route?, response: Response?): Request? {
        if (response == null) return null
        val originalRequest = response.request()
        if (originalRequest.header("Authorization") != null) return null // Already failed to authenticate
        if (!isTokenValid()) { // Check if token is saved locally
            synchronized(this) {
                if (!isTokenValid()) { // Double check if another thread already saved a token locally
                    val jwt = retrieveToken() // HTTP call to get token
                    saveToken(jwt)
                }
            }
        }
        return originalRequest.newBuilder()
                .header("Authorization", getToken())
                .build()
    }

}

您甚至可以为这种情况编写单元测试!

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32354098

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档