OkHttp全局刷新token

OkHttp全局刷新token

前言:公司項目採用token驗證,要求token失效後,能夠自動刷新,並且如果有其他網絡請求,能夠用這個刷新後的token繼續請求數據。
知識介紹:token分爲access_token和refresh_token,access_token有效期爲2個小時,refresh_token有效期爲15天。access_token失效後,需要用refresh_token進行刷新。關於token機制可以看文章基於 Token 的身份驗證
解決方法:
1.通過攔截器,獲取返回的數據
2.判斷token是否過期
3.如果token過期則刷新token
4.使用最新的token,重新請求網絡數據
5.關於重複請求token的問題

1、新建token攔截器,獲取返回的數據

public class TokenInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Response response = chain.proceed(requestBuilder.build());
        if (isTokenExpired(response)) {//根據和服務端的約定判斷token過期
        String newToken = getNewToken();
        if (!TextUtils.isEmpty(newToken)){
            //使用新的Token,創建新的請求
             Request newRequest = chain.request()
                        .newBuilder()
                        .removeHeader(AppConstant.AUTH_HEADER)
                        .addHeader(AppConstant.AUTH_HEADER, "bearer "+newToken)
                        .addHeader("Accept","application/json;version=1.2")
                        .build();
                //重新請求
                return chain.proceed(newRequest);
        } else {
            //退出app到登錄頁面,重新登錄
        }
    }
 }

2、根據與後臺商定的token過期碼,判斷是否token過期

private boolean isTokenExpired(Response response) {
        if(response.code() == 401) {
            ResponseBody body = response.body();
            if (body != null){
                try {
                    MediaType mediaType = body.contentType();
                    if (mediaType != null) {
                        if(isText(mediaType)) {
                            String resp = body.string();
                            PostOkModel model = new Gson().fromJson(resp, PostOkModel.class);
                            //4061是與後臺商量的token失效後的錯誤碼,具體應根據自己的項目決定
                            if(model.getCode() == 4601) {
                                return true;
                            }
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    private boolean isText(MediaType mediaType)
    {
        if (mediaType.type() != null && mediaType.type().equals("text"))
        {
            return true;
        }
        if (mediaType.subtype() != null)
        {
            if (mediaType.subtype().equals("json") ||
                    mediaType.subtype().equals("xml") ||
                    mediaType.subtype().equals("html") ||
                    mediaType.subtype().equals("webviewhtml")
                    )
                return true;
        }
        return false;
    }

3、token過期則進行刷新token

private static String getNewToken() throws IOException{
        String refreshToken = SPDtadUtils.getString(UserApplication.getInstance(), "refreshToken");//之前存儲在本地的refreshToken
       TokenRefreshPostModel model = new TokenRefreshPostModel();
        model.refresh_token = refreshToken;
        model.client_id = BuildConfig.CLIENT_ID;//項目中的身份id,根據自己實際情況定
        RequestBody body = RequestBody.create(AppConstant.MEDIA_TYPE_JSON, new Gson().toJson(model));
        Request request = new Request.Builder().url(AppConstant.TOKEN_REFRESH).post(body).build();
        Response newResponse = new OkHttpClient().newCall(request).execute();
        Log.e("TAG", "刷新token");
        if(newResponse.code() != 200) {
           return null;
        }
        ResponseBody responseBody = newResponse.body();
        //以下代碼爲將token存儲到本地
        TokenInfoModel tokenInfoModel = new Gson().fromJson(responseBody.string(), TokenInfoModel.class);
        UserUtils.saveToken(UserApplication.getInstance(), tokenInfoModel);
        SPDtadUtils.putString(UserApplication.getInstance(), "new_token", tokenInfoModel.getAccess_token());
        Log.e("TAG", "存儲token");
        //返回刷新後的token
        return tokenInfoModel.getAccess_token();
    }

4、使用最新的token,重新進行網絡請求

//使用新的Token,創建新的請求
             Request newRequest = chain.request()
                        .newBuilder()
                        .removeHeader(AppConstant.AUTH_HEADER)
                        .addHeader(AppConstant.AUTH_HEADER, "bearer "+newToken)
                        .addHeader("Accept","application/json;version=1.2")
                        .build();
                //重新請求
                return chain.proceed(newRequest);

5、關於重複請求token的問題

每一個網絡請求都有一個自己的攔截器,那如何實現當一個網絡請求正在刷新token的時候,其他網路請求需要等待;然後等token刷新後,其他的網路請求再使用這個刷新後的token呢。這時候就要用到java中的同步鎖。關於同步鎖的具體內容,請查看相關的知識。
加上同步鎖以後的最終代碼就是一下:

public class TokenInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        String tokenHeader = chain.request().header(ApiConstants.AUTH_HEADER_VALUE);
        GlobalDoctorData user = UserApplication.getInstance().getDoctorData();
        Request.Builder requestBuilder = chain
                .request()
                .newBuilder();
        if(TextUtils.isEmpty(tokenHeader) && user !=null && !TextUtils.isEmpty(user.getToken())) {
            requestBuilder
                    .removeHeader(AppConstant.AUTH_HEADER)
                    .addHeader(AppConstant.AUTH_HEADER, user.getToken())
                    .addHeader("Accept","application/json;version=1.2")
                    .build();
        }
        Response response = chain.proceed(requestBuilder.build());
        Log.e("TokenInteceptor", "response.code=" + response.code());
        if (isTokenExpired(response)) {//根據和服務端的約定判斷token過期
            Log.e("TokenInteceptor", "靜默自動刷新Token,然後重新請求數據");
            //同步請求方式,獲取最新的Token
            SPDtadUtils.putString(UserApplication.getInstance(), "new_token", "");
            String newToken = getNewToken();
            if(TextUtils.isEmpty(newToken)) {
                UserApplication.getInstance().userLogout(true);
                throw new IOException(UserApplication.getInstance().getResources().getString(R.string.token_fail));
            }else {
                //使用新的Token,創建新的請求
                Request newRequest = chain.request()
                        .newBuilder()
                        .removeHeader(AppConstant.AUTH_HEADER)
                        .addHeader(AppConstant.AUTH_HEADER, "bearer "+newToken)
                        .addHeader("Accept","application/json;version=1.2")
                        .build();
                //重新請求
                return chain.proceed(newRequest);
            }
        }

        return response;
    }

    private synchronized static String getNewToken() throws IOException{
        Log.e("TAG", "執行上鎖");
        String refreshToken = SPDtadUtils.getString(UserApplication.getInstance(), "refreshToken");
        if(TextUtils.isEmpty(refreshToken)) {
            return null;
        }
        String new_token = SPDtadUtils.getString(UserApplication.getInstance(), "new_token");
        if(!TextUtils.isEmpty(new_token)) {
            return new_token;
        }
        TokenRefreshPostModel model = new TokenRefreshPostModel();
        model.refresh_token = refreshToken;
        model.client_id = BuildConfig.CLIENT_ID;
        RequestBody body = RequestBody.create(AppConstant.MEDIA_TYPE_JSON, new Gson().toJson(model));
        Request request = new Request.Builder().url(AppConstant.TOKEN_REFRESH).post(body).build();
        Response newResponse = new OkHttpClient().newCall(request).execute();
        Log.e("TAG", "刷新token");
        if(newResponse.code() != 200) {
            //退出登錄並返回到登錄頁面的邏輯
            SPDtadUtils.putString(UserApplication.getInstance(), "refreshToken", "");
            return null;
        }
        ResponseBody responseBody = newResponse.body();
        TokenInfoModel tokenInfoModel = new Gson().fromJson(responseBody.string(), TokenInfoModel.class);
        UserUtils.saveToken(UserApplication.getInstance(), tokenInfoModel);
        SPDtadUtils.putString(UserApplication.getInstance(), "new_token", tokenInfoModel.getAccess_token());
        Log.e("TAG", "存儲token");
        return tokenInfoModel.getAccess_token();
    }

    private boolean isTokenExpired(Response response) {
        if(response.code() == 401) {
            ResponseBody body = response.body();
            if (body != null){
                try {
                    MediaType mediaType = body.contentType();
                    if (mediaType != null) {
                        if(isText(mediaType)) {
                            String resp = body.string();
                            PostOkModel model = new Gson().fromJson(resp, PostOkModel.class);
                            if(model.getCode() == 4601) {
                                return true;
                            }
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    private boolean isText(MediaType mediaType)
    {
        if (mediaType.type() != null && mediaType.type().equals("text"))
        {
            return true;
        }
        if (mediaType.subtype() != null)
        {
            if (mediaType.subtype().equals("json") ||
                    mediaType.subtype().equals("xml") ||
                    mediaType.subtype().equals("html") ||
                    mediaType.subtype().equals("webviewhtml")
                    )
                return true;
        }
        return false;
    }
}

總結:以上就是我在項目中用的刷新token的方法。如果有不對的地方或者可以改進的地方,麻煩請告知,謝謝!

參考博文:
http://www.jianshu.com/p/8d1ee61bc2d2
http://www.jianshu.com/p/62ab11ddacc8

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章