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

前言

最近公司在改造接口的請求的驗證,以前是登錄後返回一個token,在請求的時候動態添加到header中,以此來驗證身份,當返回401直接去從新登陸;如今登陸返回tokenrefreshToken兩個參數,拿token去添加header,當返回401時並不直接去登陸而是拿refreshToken去請求一個接口,刷新獲得新的tokenrefreshToken,拿到新的token再去請求當前返回401的接口,若是此時返回410則是真正的過時才須要去登陸。json

準備工做

  • 首先,由於以前用了okhttp的攔截器,我想到的仍是在的攔截器中處理;
  • 其次,去網上搜一波兒看看各位大神是怎麼實現的!嗯?你猜的沒錯,英雄所見略同,基本就是這個方案;
  • 最後,固然是開始編碼了。

正式工做

  • 重寫攔截器,繼承自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真正的過時了,直接去從新登陸。bash

  • 從新請求,咱們此時只須要拿到上次請求的request,由於咱們攔截了響應當前攔截器中的request就是咱們以前報401的請求,可是\color{red}{咱們此時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了,那就直接拿最新的newToken去從新請求當前接口就行了,咱們拿到最新的token確定是須要保持成全局的,而咱們全部的請求是異步的,那就能夠拿到每次的request,這意味着什麼?咱們就能夠拿到header,那以前過時的token就有了;兩者一對比,同樣則說明尚未刷新過token,那就先去刷新token,不同說明已經有接口刷新過了直接拿最新newToken的去從新請求就行了。(就是一個判斷就不貼代碼了【偷笑】)app

  • 可是這裏我出現了一個問題,經過下面這個方法並拿不到,我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,並且不爲空哦,這確定能夠成功取到
String oldToken = response.request().headers().get("Authorization");
複製代碼
  • 因而有了下面的結果
if ("401".equals(code)) {
            synchronized (mContext) {
                refreshToken = "獲取最新的refreshToken"
                token = "獲取最新的token"
                String oldToken = response.request().headers().get("Authorization");

                /**
                 * 當前請求中的jwt和本地最新的是否同樣:
                 * 一、同樣則說明沒有進行刷新token操做不進入此 if
                 * 二、不同則說明已經刷新過token操做了,進入此 if 拿最新的token直接從新發起當前的請求
                 */
                if (!token.equals(oldToken)) {
                    Request.Builder newBuilder = getBuilder(chain.request(), token);
                    return getNewResponse(chain, newBuilder);
                }

                Map<String, String> mapToken = refreshMapToken(refreshToken);
                String newToken = mapToken.get("token");
                String newRefreshToken = mapToken.get("refreshToken");
                MyApplication.setToken(newToken);//設置爲全局常量
                "此處還須要的一個操做是把兩者都保存到本地,否則下次登陸就沒了"

                Request.Builder newBuilder = getBuilder(chain.request(), newToken);
                return getNewResponse(chain, newBuilder);
            }
        }
複製代碼
  • 當併發來了,因此請求都會報401時,只會有最早的一次來的去刷新token,達到想要的效果了通過反覆以及併發的測試,這回算是真的沒問題了。

後記:多試試、多看看、多想一想,問題總會解決的;關於參考博客,我也不知道是哪一篇了,十分感謝;若有紕漏,不吝賜教!

相關文章
相關標籤/搜索