我在我的android应用程序中使用了OkHttp,其中有几个异步请求。所有请求都需要与报头一起发送令牌。有时我需要使用RefreshToken刷新令牌,所以我决定使用OkHttp的Authenticator类。
当2个或更多异步请求同时从服务器获得401响应代码时,会发生什么情况?是为每个请求调用验证器的authenticate()方法,还是只为获得401的第一个请求调用一次?
@Override
public Request authenticate(Proxy proxy, Response response) throws IOException
{
return null;
}如何只刷新token一次?
发布于 2020-02-09 04:24:17
API使用单例Authenticator
Synchronized
这是一个Kotlin语言的示例
@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 ")
}
}发布于 2016-10-19 17:43:40
我在这里看到了两个场景,基于你调用的API的工作方式。
第一个绝对更容易处理-调用新的凭证(例如访问令牌)不会使旧的凭证过期。要实现这一点,您可以向凭据添加一个额外的标志,以表明凭据正在被刷新。当您获得401响应时,您将flag设置为true,发出一个请求以获取新的凭据,并且仅当flag等于true时才保存它们,以便只处理第一个响应,其余的将被忽略。确保您对flag的访问是同步的。
另一种情况有点棘手-每次调用新凭据时,服务器端都会将旧凭据设置为过期。为了处理它,我会引入一个新的对象作为semafore它会在每次“凭证被刷新”时被阻塞。为了确保只进行一次“刷新凭证”调用,您需要在与flag同步的代码块中调用它。它可以看起来像这样:
synchronized(stateObject) {
if(!stateObject.isBeingRefreshed) return;
Response response = client.execute(request);
apiClient.setCredentials(response.getNewCredentials());
stateObject.isBeingRefreshed = false;
}正如您已经注意到的,有一个额外的检查if(!stateObject.isBeingRefreshed) return;,用于通过以下请求来取消请求新凭据,该请求收到了401响应。
发布于 2018-09-26 16:19:52
这是我的解决方案,以确保在多线程的情况下,使用okhttp3.Authenticator仅刷新令牌一次
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()
}
}您甚至可以为这种情况编写单元测试!
https://stackoverflow.com/questions/32354098
复制相似问题