- android - RelativeLayout 背景可绘制重叠内容
- android - 如何链接 cpufeatures lib 以获取 native android 库?
- java - OnItemClickListener 不起作用,但 OnLongItemClickListener 在自定义 ListView 中起作用
- java - Android 文件转字符串
场景:我正在使用 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/
我正在开发一个应用程序,它使用 OAuth - 基于 token 的身份验证。 考虑到我们拥有访问和刷新 token ,这就是流程的样子。 Api call -> intercepter append
如何取消标记此代码的输出? 类(class)核心: def __init__(self, user_input): pos = pop(user_input) subject = ""
当我使用命令 kubectl 时与 --token标记并指定 token ,它仍然使用 kubeconfig 中的管理员凭据文件。 这是我做的: NAMESPACE="default" SERVICE
我正在制作 SPA,并决定使用 JWT 进行身份验证/授权,并且我已经阅读了一些关于 Tokens 与 Cookies 的博客。我了解 cookie 授权的工作原理,并了解基本 token 授权的工作
我正在尝试从应用服务获取 Google 的刷新 token ,但无法。 日志说 2016-11-04T00:04:25 PID[500] Verbose Received request: GET h
我正在开发一个项目,只是为了为 java 开发人员测试 eclipse IDE。我是java新手,所以我想知道为什么它不起作用,因为我已经知道该怎么做了。这是代码: public class ecli
我正在尝试使用 JwtSecurityTokenHandler 将 token 字符串转换为 jwt token 。但它出现错误说 IDX12709: CanReadToken() returned
我已阅读文档 Authentication (来自 Facebook 的官方)。我仍然不明白 Facebook 提供的这三种访问 token 之间的区别。网站上给出了一些例子,但我还是不太明白。 每个
我的部署服务器有时有这个问题,这让我抓狂,因为我无法在本地主机中重现,我已经尝试在我的 web.config 中添加机器 key ,但没有成功远。 它只发生在登录页面。 我的布局:
我已经设法获得了一个简单的示例代码,它可以创建一个不记名 token ,还可以通过阅读 stackoverflow 上的其他论坛来通过刷新 token 请求新的不记名 token 。 启动类是这样的
如果我有以前的刷新 token 和使用纯 php 的访问 token ,没有 Google Api 库,是否可以刷新 Google Api token ?我在数据库中存储了许多用户刷新和访问 toke
我通过 Java 应用程序使用 Google 电子表格时遇到了问题。我创建了应用程序,该应用程序运行了 1 年多,没有任何问题,我什至在 Create Spreadsheet using Google
当我有一个有效的刷新 token 时,我正在尝试使用 Keycloak admin REST API 重新创建访问 token 。 我已经通过调用 POST/auth/realms/{realm}/p
我正在尝试让第三方 Java 客户端与我编写的 WCF 服务进行通信。 收到消息时出现如下异常: Cannot find a token authenticator for the 'System.I
在尝试将数据插入到我的 SQl 数据库时,我收到以下错误 System.Data.SqlServerCe.SqlCeException: There was an error parsing the
使用数据库 session token 系统,我可以让用户使用用户名/密码登录,服务器可以生成 token (例如 uuid)并将其存储在数据库中并将该 token 返回给客户端。其上的每个请求都将包
我最近注册了 Microsoft Azure 并设置了认知服务帐户。使用 Text Translation API Documentation 中的说明我能够使用 interactive online
我使用 IAntiforgery API 创建了一个 ASP.Net Core 2 应用程序。 这提供了一种返回 cookie 的方法。 客户端获取该 cookie,并在后续 POST 请求中将该值放
我正在使用 spacy 来匹配某些文本(意大利语)中的特定表达式。我的文本可以多种形式出现,我正在尝试学习编写一般规则的最佳方式。我有如下 4 个案例,我想写一个适用于所有案例的通用模式。像这样的东西
我无法理解 oauth 2.0 token 的原则处理。 我的场景是,我有一个基于 web 的前端后端系统,带有 node.js 和 angular 2。用户应该能够在此站点上上传视频。然后创建一些额
我是一名优秀的程序员,十分优秀!