gpt4 book ai didi

Android OkHttp,刷新过期 token

转载 作者:可可西里 更新时间:2023-11-01 18:45:11 24 4
gpt4 key购买 nike

场景:我正在使用 OkHttp/Retrofit 访问网络服务:同时发送多个 HTTP 请求。在某个时候,授权 token 会过期,多个请求将收到 401 响应。

问题:在我的第一个实现中,我使用了一个拦截器(此处已简化)并且每个线程都尝试刷新 token 。这会导致一团糟。

public class SignedRequestInterceptor implements Interceptor {

@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();

// 1. sign this request
request = request.newBuilder()
.header(AUTH_HEADER_KEY, BEARER_HEADER_VALUE + token)
.build();


// 2. proceed with the request
Response response = chain.proceed(request);

// 3. check the response: have we got a 401?
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {

// ... try to refresh the token
newToken = mAuthService.refreshAccessToken(..);


// sign the request with the new token and proceed
Request newRequest = request.newBuilder()
.removeHeader(AUTH_HEADER_KEY)
.addHeader(AUTH_HEADER_KEY, BEARER_HEADER_VALUE + newToken.getAccessToken())
.build();

// return the outcome of the newly signed request
response = chain.proceed(newRequest);

}

return response;
}
}

所需的解决方案:所有线程都应等待一个 token 刷新:第一个失败的请求触发刷新,并与其他请求一起等待新 token 。

处理此问题的好方法是什么? OkHttp 的一些内置功能(如 Authenticator)有帮助吗?感谢您的任何提示。

最佳答案

我遇到了同样的问题,我设法使用 ReentrantLock 解决了它.

import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;

public class RefreshTokenInterceptor implements Interceptor {

private Lock lock = new ReentrantLock();

@Override
public Response intercept(Interceptor.Chain chain) throws IOException {

Request request = chain.request();
Response response = chain.proceed(request);

if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {

// first thread will acquire the lock and start the refresh token
if (lock.tryLock()) {
Timber.i("refresh token thread holds the lock");

try {
// this sync call will refresh the token and save it for
// later use (e.g. sharedPreferences)
authenticationService.refreshTokenSync();
Request newRequest = recreateRequestWithNewAccessToken(chain);
return chain.proceed(newRequest);
} catch (ServiceException exception) {
// depending on what you need to do you can logout the user at this
// point or throw an exception and handle it in your onFailure callback
return response;
} finally {
Timber.i("refresh token finished. release lock");
lock.unlock();
}

} else {
Timber.i("wait for token to be refreshed");
lock.lock(); // this will block the thread until the thread that is refreshing
// the token will call .unlock() method
lock.unlock();
Timber.i("token refreshed. retry request");
Request newRequest = recreateRequestWithNewAccessToken(chain);
return chain.proceed(newRequest);
}
} else {
return response;
}
}

private Request recreateRequestWithNewAccessToken(Chain chain) {
String freshAccessToken = sharedPreferences.getAccessToken();
Timber.d("[freshAccessToken] %s", freshAccessToken);
return chain.request().newBuilder()
.header("access_token", freshAccessToken)
.build();
}
}

使用此解决方案的主要优点是您可以使用 mockito 编写单元测试并对其进行测试。您将必须启用 Mockito 孵化功能以模拟 final类(来自 okhttp 的响应)。阅读更多关于 here 的信息.测试看起来像这样:

@RunWith(MockitoJUnitRunner.class)
public class RefreshTokenInterceptorTest {

private static final String FRESH_ACCESS_TOKEN = "fresh_access_token";

@Mock
AuthenticationService authenticationService;

@Mock
RefreshTokenStorage refreshTokenStorage;

@Mock
Interceptor.Chain chain;

@BeforeClass
public static void setup() {
Timber.plant(new Timber.DebugTree() {

@Override
protected void log(int priority, String tag, String message, Throwable t) {
System.out.println(Thread.currentThread() + " " + message);
}
});
}

@Test
public void refreshTokenInterceptor_works_as_expected() throws IOException, InterruptedException {

Response unauthorizedResponse = createUnauthorizedResponse();
when(chain.proceed((Request) any())).thenReturn(unauthorizedResponse);
when(authenticationService.refreshTokenSync()).thenAnswer(new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
//refresh token takes some time
Thread.sleep(10);
return true;
}
});
when(refreshTokenStorage.getAccessToken()).thenReturn(FRESH_ACCESS_TOKEN);
Request fakeRequest = createFakeRequest();
when(chain.request()).thenReturn(fakeRequest);

final Interceptor interceptor = new RefreshTokenInterceptor(authenticationService, refreshTokenStorage);

Timber.d("5 requests try to refresh token at the same time");
final CountDownLatch countDownLatch5 = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
interceptor.intercept(chain);
countDownLatch5.countDown();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
}
countDownLatch5.await();

verify(authenticationService, times(1)).refreshTokenSync();


Timber.d("next time another 3 threads try to refresh the token at the same time");
final CountDownLatch countDownLatch3 = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
interceptor.intercept(chain);
countDownLatch3.countDown();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
}
countDownLatch3.await();

verify(authenticationService, times(2)).refreshTokenSync();


Timber.d("1 thread tries to refresh the token");
interceptor.intercept(chain);

verify(authenticationService, times(3)).refreshTokenSync();
}

private Response createUnauthorizedResponse() throws IOException {
Response response = mock(Response.class);
when(response.code()).thenReturn(401);
return response;
}

private Request createFakeRequest() {
Request request = mock(Request.class);
Request.Builder fakeBuilder = createFakeBuilder();
when(request.newBuilder()).thenReturn(fakeBuilder);
return request;
}

private Request.Builder createFakeBuilder() {
Request.Builder mockBuilder = mock(Request.Builder.class);
when(mockBuilder.header("access_token", FRESH_ACCESS_TOKEN)).thenReturn(mockBuilder);
return mockBuilder;
}

}

关于Android OkHttp,刷新过期 token ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31021725/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com