天天看點

接口請求身份認證的Token和RefreshToken的解決方案

前言

最近公司在改造接口的請求的驗證,之前是登陸後傳回一個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了,這肯定不是我想要的效果。
接口請求身份認證的Token和RefreshToken的解決方案
  • 不慌哈,慌也要把問題解決了,其實也沒有那麼複雜,上網搜尋一波兒,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的解決方案

後記:多試試、多看看、多想想,問題總會解決的;關于參考部落格,我也不知道是哪一篇了,十分感謝;如有纰漏,不吝賜教!