前言
最近公司在改造接口的請求的驗證,之前是登陸後傳回一個token,在請求的時候動态添加到header中,以此來驗證身份,當傳回401直接去重新登入;現在登入傳回token和refreshToken兩個參數,拿token去添加header,當傳回401時并不直接去登入而是拿refreshToken去請求一個接口,重新整理得到新的token和refreshToken,拿到新的token再去請求目前傳回401的接口,如果此時傳回410則是真正的過期才需要去登入。
準備工作
- 首先,因為之前用了okhttp的攔截器,我想到的還是在的攔截器中處理;
- 其次,去網上搜一波兒看看各位大神是怎麼實作的!嗯?你猜的沒錯,英雄所見略同,基本就是這個方案;
- 最後,當然是開始編碼了。
接口請求身份認證的Token和RefreshToken的解決方案
正式工作
- 重寫攔截器,繼承自Interceptor,在okhttp3.Interceptor結構下;
- 既然是傳回401,在攔截器中去攔截我們的response,判斷響應碼不是HTTP的狀态碼,是我們和背景約定的狀态碼
Response response = chain.proceed(builder.build());
ResponseBody responseBody = response.body();
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE);
Buffer buffer = source.buffer();
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
//擷取響應體的字元串
String bodyString = buffer.clone().readString(charset);
CustomResponse customResponse = new Gson().fromJson(bodyString, CustomResponse.class);
String code = customResponse.getCode();//背景的傳回碼
String msg = customResponse.getMsg();
if ("401".equals(code)) {
//todo 當傳回401時去重新整理token
}
//否則正常傳回 response
- 重新整理token,這是一個新的接口;我目前的請求是異步的,我們要攔截響應,是以我們重新整理操作的接口必須是同步請求,必須要拿到結果才能去後續操作
Map<String, String> map = new ArrayMap<>();
map.put("refreshToken", refreshToken);//這是我們在登入成功後傳回的refreshToken,專門用于重新整理操作的
RequestBody body = NetworkUtils.setBody(map);
Call<CustomResponse<Map<String, String>>> call = RetrofitUtils.provideClientApi().refreshToken(body);
CustomResponse refreshResponse = call.execute().body();
Map<String, String> mapToken = (Map<String, String>) refreshResponse.getData();
String refreshCode = refreshResponse.getCode();
- 重新整理成功後有兩種操作,如果傳回200,拿到新的token去重新請求目前報401的接口,如果傳回410(當然也可以是110,因為這是咋們和背景小夥伴約定的這個時候就是token真正的過期了,直接去重新登入。
- #####重新請求,我們此時隻需要拿到上次請求的request,因為我們攔截了響應目前攔截器中的request就是我們之前報401的請求,但是 我 們 此 時 b u i l d e r 中 的 h e a d e r 還 是 我 之 前 過 期 的 t o k e n , 需 要 用 我 們 的 新 的 n e w T o k e n 替 換 掉 \color{red}{我們此時builder中的header還是我之前過期的token,需要用我們的新的newToken替換掉} 我們此時builder中的header還是我之前過期的token,需要用我們的新的newToken替換掉,然後傳回response,也可以在這個response中繼續攔截操作
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();//這裡的request隻是為了拿到請求的url和參數,下面要重新生成request(builder.build())
Request.Builder builder = request.newBuilder()
.addHeader("Content-Type", "application/json; charset=UTF-8")
.addHeader("Authorization", newToken);
//注意:chain.proceed(這裡一定不能是拿到的request,而是builder.build())
return chain.proceed(builder.build());
}
好了,完成
- 你真以為就這樣完了,那你還是要天真,我當時就是這樣想的;
- 我有好幾個接口并發來了,事實證明這樣行不通的,會是個什麼效果呢?
- 後果就是:每個接口都報401時都去重新整理token,然後一直就這樣循環下去,最後就報410了,這肯定不是我想要的效果。
- 不慌哈,慌也要把問題解決了,其實也沒有那麼複雜,上網搜尋一波兒,5分鐘後有方案了,加鎖啊,真是機制如我【手動滑稽】,具體就是把重新整理token這塊代碼同步起來
synchronized (mContext) {
Map<String, String> map = new ArrayMap<>();
map.put("refreshToken", refreshToken);//這是我們在登入成功後傳回的refreshToken,專門用于重新整理操作的
RequestBody body = NetworkUtils.setBody(map);
Call<CustomResponse<Map<String, String>>> call = RetrofitUtils.provideClientApi().refreshToken(body);
CustomResponse refreshResponse = call.execute().body();
Map<String, String> mapToken = (Map<String, String>) refreshResponse.getData();
String refreshCode = refreshResponse.getCode();
}
- 但是好想還是沒有解決啊,這最多就是不會同時去調重新整理token,最後還是每個報401的請求還是會去重新整理
接口請求身份認證的Token和RefreshToken的解決方案
其實仔細想想,如果我們已經重新整理過token了,那就直接拿最新的newToken去重新請求目前接口就好了,我們拿到最新的token肯定是需要保持成全局的,而我們所有的請求是異步的,那就可以拿到每次的request,這意味着什麼?我們就可以拿到header,那之前過期的token就有了;二者一對比,一樣則說明還沒有重新整理過token,那就先去重新整理token,不一樣說明已經有接口重新整理過了直接拿最新newToken的去重新請求就好了。(就是一個判斷就不貼代碼了【偷笑】)
- 但是這裡我出現了一個問題,通過下面這個方法并拿不到,我debug發現這個header是為空的
String oldToken = request.header("Authorization");
String oldToken = request.headers().get("Authorization");
- 再回去看我們的添加header的地方,其實我們的header是添加在builder中的,但是似乎拿不到,可能是我姿勢不對(我坐着取的,以後有機會躺着試試)
Request.Builder builder = request.newBuilder()
.addHeader("Content-Type", "application/json; charset=UTF-8")
.addHeader("Authorization", newToken);
- 通過debug發現,response裡面也有request,而且不為空哦,這肯定可以成功取到
- 于是有了下面的結果
if ("401".equals(code)) {
synchronized (mContext) {
refreshToken = "擷取最新的refreshToken"
token = "擷取最新的token"
String oldJwt = response.request().headers().get("Authorization");
/**
* 目前請求中的jwt和本地最新的是否一樣:
* 1、一樣則說明沒有進行重新整理jwt操作不進入此 if
* 2、不一樣則說明已經重新整理過jwt操作了,進入此 if 拿最新的jwt直接重新發起目前的請求
*/
if (!jwt.equals(oldJwt)) {
Request.Builder newBuilder = getBuilder(chain.request(), token);
return getNewResponse(chain, newBuilder);
}
Map<String, String> mapToken = refreshMapJwt(refreshJwt);
String newToken = mapJwt.get("token");
String newRefreshToken = mapToken.get("refreshToken");
MyApplication.setToken(newToken);//設定為全局常量
"此處還需要的一個操作是把二者都儲存到本地,不然下次登入就沒了"
Request.Builder newBuilder = getBuilder(chain.request(), newJwt);
return getNewResponse(chain, newBuilder);
}
}
- 當并發來了,是以請求都會報401時,隻會有最先的一次來的去重新整理token,達到想要的效果了經過反複以及并發的測試,這回算是真的沒問題了。
接口請求身份認證的Token和RefreshToken的解決方案
後記:多試試、多看看、多想想,問題總會解決的;關于參考部落格,我也不知道是哪一篇了,十分感謝;如有纰漏,不吝賜教!