在 okhttp3 拦截器中是否有等待刷新令牌的方法?
Is there a method to wait for a refreshed token in okhttp3 interceptor?
最近我不得不处理一个使用 Retrofit 1、okhttp3、jobManager 和 Picasso 2.71828 的大型旧项目
应用程序从服务器接收数据。
交互逻辑:用户登录,接收token,刷新token。它们与 shHelper 一起存储在 SharedPreferences 中。有了令牌,他可以发送请求(他在 url 中的某个地方,在 body 中的某个地方),在刷新令牌的帮助下,如果 session被重置或token烂掉
授权错误 (401) 由我们设法与 Picasso 一起使用的 okhttp3 身份验证器处理。
但是有一个问题 - 如果屏幕上有几张图片,Picasso - 会同时或几乎同时连续发送多个请求,并且由于它们都立即收到 401 答案,如果令牌已损坏,验证器会立即发送相同的数字更新令牌的请求。
是否有一些优雅的方法来等待令牌更新,然后重复对其余图片的请求?现在它发生如下 - 收到错误 401,令牌重置为零 (token = "") 并且所有其他落入验证器的流检查是否 (token == "") 执行 Thread.sleep ()而且我很不满意
private Authenticator getAuthenticator() {
return (route, response) -> {
if (errorCount > 3){
return null;
}
if (response.request().url().toString().endsWith("/refreshToken")) {
Log.d(TAG, "getAuthenticator: " + "refreshToken");
PasswordRepeatActivity.start(context);
return null;
}
if (response.request().url().toString().endsWith("/auth")) {
String message = "Попробуйте позже";
try {
com.google.gson.Gson gson = Gson.builder().create();
ApiResponse apiError = gson.fromJson(response.body().string(), ApiResponse.class);
message = apiError.getMessage();
} catch (Exception e) {
e.printStackTrace();
}
throw new IOException(message);
}
String login = spHelper.getCurrentLogin();
Auth auth = spHelper.getAuth(login);
String token = auth.getToken();
HttpUrl oldUrl = response.request().url();
//if token is empty - repeat checking after some time
Log.d(TAG, "getAuthenticator: token ==" + token);
if (token != null && token.isEmpty()) {
boolean isEmpty = true;
while (isEmpty){
try {
Log.d(TAG, "Authenticator: sleeping...");
Thread.sleep(500);
String mToken = spHelper.getAuth(login).getToken();
if (mToken!= null && !mToken.isEmpty()){
isEmpty = false;
}
Log.d(TAG, "Authenticator: check if token is refreshed");
if (!mToken.isEmpty() && oldUrl.toString().contains("token") && !mToken.equals(oldUrl.queryParameter("token"))) {
Log.d(TAG, "Authenticator: token is valid, token: " + mToken);
return getRefreshedUrlRequest(mToken, oldUrl);
}
} catch (InterruptedException e) {
e.printStackTrace();
return response.request();
}
}
return response.request();
} else if (oldUrl.toString().contains("token") && !token.equals(oldUrl.queryParameter("token"))) {
Log.d(TAG, "Authenticator: token is valid, token: " + token);
return getRefreshedUrlRequest(token, oldUrl);
} else {
auth.clearToken();
spHelper.putAuth(login, auth);
String refreshToken = auth.getRefreshToken();
RefreshRequest refreshRequest = new RefreshRequest(refreshToken);
try {
AuthResponse refreshResponse = dataApi.refresh(refreshRequest);
errorCount = 0;
Auth newAuth = refreshResponse.getResponse();
spHelper.putAuth(login, newAuth);
Request request = response.request();
RequestBody requestBody = request.body();
String newToken = newAuth.getToken();
Log.d(TAG, "Authenticator: token refreshed, old token: " + token + " -> " + "new token : " + newToken);
if (oldUrl.toString().contains("token")) {
return getRefreshedUrlRequest(newToken, oldUrl);
}
if (requestBody != null
&& requestBody.contentType() != null
&& requestBody.contentType().subtype() != null
&& requestBody.contentType().subtype().contains("json")) {
requestBody = processApplicationJsonRequestBody(requestBody, newToken);
}
if (requestBody != null) {
Request.Builder requestBuilder = request.newBuilder();
request = requestBuilder
.post(requestBody)
.build();
} else {
LoginActivity.show(context);
}
return request;
} catch (RequestException e) {
AtlasPatienteLog.d(TAG, "Can't refresh token: " + e.getMessage());
return response.request();
}
}
};
}
我正在寻找在第一个错误 401 之后发送一个请求以刷新令牌并与所有其他线程一起等待它,然后使用新令牌发送请求的方法。
除了在验证器中等待更新的令牌外,还有什么办法可以以某种方式简化这段代码吗?现在这个方法大约有 100 行长,每次需要更改它时 - 即使阅读和保持头脑中的逻辑也成为问题。
因此,经过一段时间和一些尝试后,我将身份验证器的部分同步到某个锁上 object。现在只有一个线程可以访问身份验证器。因此,如果令牌需要 bs 刷新 - 它将会刷新,并且在刷新所有等待新令牌的线程后将使用新令牌重复它们的调用。
感谢@Yuri Schimke 分享非常有用的信息。
private Authenticator getAuthenticator() {
return (route, response) -> {
String responseUrl = response.request().url().toString();
if (responseUrl.endsWith("/refreshToken") ) {
Log.d(TAG, "getAuthenticator: " + "refreshToken");
PasswordRepeatActivity.start(context);
return null;
}
if (responseUrl.endsWith("/auth")) {
String message = "Попробуйте позже";
try {
com.google.gson.Gson gson = Gson.builder().create();
ApiResponse apiError = gson.fromJson(response.body().string(), ApiResponse.class);
message = apiError.getMessage();
} catch (Exception e) {
e.printStackTrace();
}
throw new IOException(message);
}
synchronized (LOCK) {
String login = spHelper.getCurrentLogin();
Auth auth = spHelper.getAuth(login);
String token = auth.getToken();
HttpUrl oldUrl = response.request().url();
if (oldUrl.toString().contains("token") && !token.equals(oldUrl.queryParameter("token"))) {
Log.d(TAG, "Authenticator: token is valid, token: " + token);
return getRefreshedUrlRequest(token, oldUrl);
} else {
String refreshToken = auth.getRefreshToken();
RefreshRequest refreshRequest = new RefreshRequest(refreshToken);
try {
AuthResponse refreshResponse = dataApi.refresh(refreshRequest);
Auth newAuth = refreshResponse.getResponse();
spHelper.putAuth(login, newAuth);
Request request = response.request();
RequestBody requestBody = request.body();
String newToken = newAuth.getToken();
Log.d(TAG, "Authenticator: token refreshed, old token: " + token + " -> " + "new token : " + newToken);
if (oldUrl.toString().contains("token")) {
return getRefreshedUrlRequest(newToken, oldUrl);
}
if (requestBody != null
&& requestBody.contentType() != null
&& requestBody.contentType().subtype() != null
&& requestBody.contentType().subtype().contains("json")) {
requestBody = processApplicationJsonRequestBody(requestBody, newToken);
}
if (requestBody != null) {
Request.Builder requestBuilder = request.newBuilder();
request = requestBuilder
.post(requestBody)
.build();
} else {
LoginActivity.show(context);
}
return request;
} catch (RequestException e) {
AtlasPatienteLog.d(TAG, "Can't refresh token: " + e.getMessage());
PasswordRepeatActivity.start(context);
return null;
}
}
}
};
}
仅使用 OkHttp,您通常需要在应用程序中处理这种复杂性,无论是在调用之外、在身份验证器中还是在主动身份验证拦截器中。在这些情况下也不会为您处理并发。
在这里讨论
https://github.com/square/okhttp/issues/3714#issuecomment-350469364
确保进行同步刷新调用,因为异步调用可能没有可执行的空闲线程。
the answer from @swankjesse was that if you make a sync call in an
interceptor, then you are tying up a thread, but won't deadlock
because it doesn't need to grab another thread and doesn't hold a lock
during that time.
一些类似主题的博客
https://objectpartners.com/2018/06/08/okhttp-authenticator-selectively-reauthorizing-requests/
https://blog.coinbase.com/okhttp-oauth-token-refreshes-b598f55dd3b2
最近我不得不处理一个使用 Retrofit 1、okhttp3、jobManager 和 Picasso 2.71828 的大型旧项目
应用程序从服务器接收数据。 交互逻辑:用户登录,接收token,刷新token。它们与 shHelper 一起存储在 SharedPreferences 中。有了令牌,他可以发送请求(他在 url 中的某个地方,在 body 中的某个地方),在刷新令牌的帮助下,如果 session被重置或token烂掉
授权错误 (401) 由我们设法与 Picasso 一起使用的 okhttp3 身份验证器处理。 但是有一个问题 - 如果屏幕上有几张图片,Picasso - 会同时或几乎同时连续发送多个请求,并且由于它们都立即收到 401 答案,如果令牌已损坏,验证器会立即发送相同的数字更新令牌的请求。 是否有一些优雅的方法来等待令牌更新,然后重复对其余图片的请求?现在它发生如下 - 收到错误 401,令牌重置为零 (token = "") 并且所有其他落入验证器的流检查是否 (token == "") 执行 Thread.sleep ()而且我很不满意
private Authenticator getAuthenticator() {
return (route, response) -> {
if (errorCount > 3){
return null;
}
if (response.request().url().toString().endsWith("/refreshToken")) {
Log.d(TAG, "getAuthenticator: " + "refreshToken");
PasswordRepeatActivity.start(context);
return null;
}
if (response.request().url().toString().endsWith("/auth")) {
String message = "Попробуйте позже";
try {
com.google.gson.Gson gson = Gson.builder().create();
ApiResponse apiError = gson.fromJson(response.body().string(), ApiResponse.class);
message = apiError.getMessage();
} catch (Exception e) {
e.printStackTrace();
}
throw new IOException(message);
}
String login = spHelper.getCurrentLogin();
Auth auth = spHelper.getAuth(login);
String token = auth.getToken();
HttpUrl oldUrl = response.request().url();
//if token is empty - repeat checking after some time
Log.d(TAG, "getAuthenticator: token ==" + token);
if (token != null && token.isEmpty()) {
boolean isEmpty = true;
while (isEmpty){
try {
Log.d(TAG, "Authenticator: sleeping...");
Thread.sleep(500);
String mToken = spHelper.getAuth(login).getToken();
if (mToken!= null && !mToken.isEmpty()){
isEmpty = false;
}
Log.d(TAG, "Authenticator: check if token is refreshed");
if (!mToken.isEmpty() && oldUrl.toString().contains("token") && !mToken.equals(oldUrl.queryParameter("token"))) {
Log.d(TAG, "Authenticator: token is valid, token: " + mToken);
return getRefreshedUrlRequest(mToken, oldUrl);
}
} catch (InterruptedException e) {
e.printStackTrace();
return response.request();
}
}
return response.request();
} else if (oldUrl.toString().contains("token") && !token.equals(oldUrl.queryParameter("token"))) {
Log.d(TAG, "Authenticator: token is valid, token: " + token);
return getRefreshedUrlRequest(token, oldUrl);
} else {
auth.clearToken();
spHelper.putAuth(login, auth);
String refreshToken = auth.getRefreshToken();
RefreshRequest refreshRequest = new RefreshRequest(refreshToken);
try {
AuthResponse refreshResponse = dataApi.refresh(refreshRequest);
errorCount = 0;
Auth newAuth = refreshResponse.getResponse();
spHelper.putAuth(login, newAuth);
Request request = response.request();
RequestBody requestBody = request.body();
String newToken = newAuth.getToken();
Log.d(TAG, "Authenticator: token refreshed, old token: " + token + " -> " + "new token : " + newToken);
if (oldUrl.toString().contains("token")) {
return getRefreshedUrlRequest(newToken, oldUrl);
}
if (requestBody != null
&& requestBody.contentType() != null
&& requestBody.contentType().subtype() != null
&& requestBody.contentType().subtype().contains("json")) {
requestBody = processApplicationJsonRequestBody(requestBody, newToken);
}
if (requestBody != null) {
Request.Builder requestBuilder = request.newBuilder();
request = requestBuilder
.post(requestBody)
.build();
} else {
LoginActivity.show(context);
}
return request;
} catch (RequestException e) {
AtlasPatienteLog.d(TAG, "Can't refresh token: " + e.getMessage());
return response.request();
}
}
};
}
我正在寻找在第一个错误 401 之后发送一个请求以刷新令牌并与所有其他线程一起等待它,然后使用新令牌发送请求的方法。 除了在验证器中等待更新的令牌外,还有什么办法可以以某种方式简化这段代码吗?现在这个方法大约有 100 行长,每次需要更改它时 - 即使阅读和保持头脑中的逻辑也成为问题。
因此,经过一段时间和一些尝试后,我将身份验证器的部分同步到某个锁上 object。现在只有一个线程可以访问身份验证器。因此,如果令牌需要 bs 刷新 - 它将会刷新,并且在刷新所有等待新令牌的线程后将使用新令牌重复它们的调用。 感谢@Yuri Schimke 分享非常有用的信息。
private Authenticator getAuthenticator() {
return (route, response) -> {
String responseUrl = response.request().url().toString();
if (responseUrl.endsWith("/refreshToken") ) {
Log.d(TAG, "getAuthenticator: " + "refreshToken");
PasswordRepeatActivity.start(context);
return null;
}
if (responseUrl.endsWith("/auth")) {
String message = "Попробуйте позже";
try {
com.google.gson.Gson gson = Gson.builder().create();
ApiResponse apiError = gson.fromJson(response.body().string(), ApiResponse.class);
message = apiError.getMessage();
} catch (Exception e) {
e.printStackTrace();
}
throw new IOException(message);
}
synchronized (LOCK) {
String login = spHelper.getCurrentLogin();
Auth auth = spHelper.getAuth(login);
String token = auth.getToken();
HttpUrl oldUrl = response.request().url();
if (oldUrl.toString().contains("token") && !token.equals(oldUrl.queryParameter("token"))) {
Log.d(TAG, "Authenticator: token is valid, token: " + token);
return getRefreshedUrlRequest(token, oldUrl);
} else {
String refreshToken = auth.getRefreshToken();
RefreshRequest refreshRequest = new RefreshRequest(refreshToken);
try {
AuthResponse refreshResponse = dataApi.refresh(refreshRequest);
Auth newAuth = refreshResponse.getResponse();
spHelper.putAuth(login, newAuth);
Request request = response.request();
RequestBody requestBody = request.body();
String newToken = newAuth.getToken();
Log.d(TAG, "Authenticator: token refreshed, old token: " + token + " -> " + "new token : " + newToken);
if (oldUrl.toString().contains("token")) {
return getRefreshedUrlRequest(newToken, oldUrl);
}
if (requestBody != null
&& requestBody.contentType() != null
&& requestBody.contentType().subtype() != null
&& requestBody.contentType().subtype().contains("json")) {
requestBody = processApplicationJsonRequestBody(requestBody, newToken);
}
if (requestBody != null) {
Request.Builder requestBuilder = request.newBuilder();
request = requestBuilder
.post(requestBody)
.build();
} else {
LoginActivity.show(context);
}
return request;
} catch (RequestException e) {
AtlasPatienteLog.d(TAG, "Can't refresh token: " + e.getMessage());
PasswordRepeatActivity.start(context);
return null;
}
}
}
};
}
仅使用 OkHttp,您通常需要在应用程序中处理这种复杂性,无论是在调用之外、在身份验证器中还是在主动身份验证拦截器中。在这些情况下也不会为您处理并发。
在这里讨论
https://github.com/square/okhttp/issues/3714#issuecomment-350469364
确保进行同步刷新调用,因为异步调用可能没有可执行的空闲线程。
the answer from @swankjesse was that if you make a sync call in an interceptor, then you are tying up a thread, but won't deadlock because it doesn't need to grab another thread and doesn't hold a lock during that time.
一些类似主题的博客
https://objectpartners.com/2018/06/08/okhttp-authenticator-selectively-reauthorizing-requests/
https://blog.coinbase.com/okhttp-oauth-token-refreshes-b598f55dd3b2