參考文章:
(五星推薦)retrofit+rxjava 刷新token併發處理 https://www.jianshu.com/p/eb7042693d52
retrofit 刷新token併發處理 https://www.jianshu.com/p/c325f5c32709
Android上使用retrofit+okhttp時token失效的處理方案 https://www.jianshu.com/p/62ab11ddacc8
OkHttp 更新 token 的解決方案 https://juejin.im/entry/584144d0128fe1006c3cfb8f
如何改變OkHttp Response中的主體?https://androidcookie.com/okhttp-response.html
最近項目中爲了實現token過期增加會話保持的概念,引入accessToken及refreshToken機制,增加對用戶的管理,accessToken一般過期時間較短,accessToken過期後,使用refreshToken獲取新的accessToken,refreshToken也有過期時間,refreshToken過期後需要退出登錄,引導用戶重新登錄。
所有接口請求的header中需要加入accessToken字段,而當accessToken過期時,會接收到服務端返回的特定錯誤碼,此時使用refreshToken調用刷新token的接口獲取新的accessToken並保存,之後的接口請求就攜帶新的accessToken;而當使用refreshToken調用刷新token時,返回refreshToken過期時,則需要退出登錄,引導用戶重新登錄,重新獲取refreshToken/accessToken。
流程圖如下:
但上面的流程有一個需要注意的地方,就是accessToken過期時。
假如在進入app主頁時會同時請求好幾個接口,那這幾個接口都會返回accessToken過期,每個接口獲取到accessToken過期,都去調用一次刷新token的接口是不合理的,應該是第一次攔截到accessToken過期時,調用刷新token接口並加鎖,當獲取到新的token保存到本地,並且之前等待的幾個接口請求替換各自參數中的accessToken使用最新獲得的accessToken發起重試。
再然後就是talk is cheap, show your code環節了
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.addInterceptor( new Interceptor() {
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
Request.Builder builder = chain.request().newBuilder();
...
String oldAccessToken = "";
if (mAccessToken != null) {
//獲取令牌
builder.addHeader("Authorization", mAccessToken);
oldAccessToken = mAccessToken;
}
RequestBody body = chain.request().body();
...
//處理accessToken失效
Response response = chain.proceed(builder.build());
if (response != null) {
ResponseBody responseBody = response.body();
if (responseBody != null) {
BufferedSource source = responseBody.source();
source.request(Integer.MAX_VALUE);
Buffer buffer = source.buffer();
Charset charset = UTF_8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF_8);
}
String bodyString = buffer.clone().readString(charset);
try {
JSONObject jsonObject = new JSONObject(bodyString);
String code = jsonObject.optString("code");
synchronized (HttpClientHelper.class) {
if (TextUtils.equals(CommonConstant.HttpResultCode.ACCESS_TOKEN_INVALID, code)) {
//比較請求的token與本地存儲的token 如果不一致 直接重試
if (!TextUtils.isEmpty(oldAccessToken) && !TextUtils.isEmpty(mAccessToken)
&& !TextUtils.equals(mAccessToken, oldAccessToken)) {
builder.header("Authorization", mAccessToken)
.build();
return chain.proceed(builder.build());
}
if (mContext != null) {
SPEditor spEditor = SPEditor.getInstance(mContext.getApplicationContext());
String refreshToken = spEditor.loadStringInfo(CommonConstant.SPKey.REFRESH_TOKEN);
if (!TextUtils.isEmpty(refreshToken)) {
RefreshTokenResponse data = service.refreshToken(refreshToken).execute().body();
if (data != null) {
//刷新token失敗 重寫原接口的請求 在原接口請求回調中處理了退出登錄
if (!TextUtils.equals(CommonConstant.HttpResultCode.SUCCESS, data.getCode()) || data.getData() == null || TextUtils.isEmpty(data.getData().getAccessToken())) {
jsonObject.put("code", data.getCode());
jsonObject.put("msg", data.getMsg());
return response.newBuilder().body(ResponseBody.create(contentType, jsonObject.toString())).build();
} else {
String accessToken = data.getData().getAccessToken();
spEditor.saveStringInfo(CommonConstant.SPKey.ACCESS_TOKEN, accessToken);
mAccessToken = accessToken;
String reTryCurrentTimeMillis = String.valueOf(System.currentTimeMillis());
builder.header("Authorization", mAccessToken)
.build();
return chain.proceed(builder.build());
}
}
}
}
}
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
return response;
}
};)
.writeTimeout(20L, TimeUnit.SECONDS)
.readTimeout(20L, TimeUnit.SECONDS)
.addInterceptor(logging);