preface

Recently, the company is transforming the authentication of the request of the interface. Previously, a token was returned after login, which was dynamically added to the header during the request to verify the identity. When 401 is returned, the user directly re-logs in. Now login returns two parameters: Token and refreshToken, and the token is used to add header. When 401 is returned, the login is not directly logged in, but the refreshToken is used to request an interface, and the new token and refreshToken are refreshed. Get the new token and then request the interface that currently returns 401. If 410 is returned at this time, it is truly expired and you need to log in.

The preparatory work

  • First, since I used the okHTTP interceptor before, I wanted to do it in the interceptor.
  • Next, go on the net search a wave to see each great god is how to come true! Huh? You guessed it, great minds think alike, that’s basically the plan;
  • Finally, of course, it’s time to start coding.

The formal work

  • Overwrite Interceptor, inheriting from Interceptor, under okHttp3. Interceptor;
  • Since it is return 401, intercept our response in the interceptor, and judge that the response code is not the HTTP status code, but the status code agreed with the background
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); BodyString = buffer.clone().readString(charset); CustomResponse customResponse = new Gson().fromJson(bodyString, CustomResponse.class); String code = customResponse.getCode(); // Background return code String MSG = customResponse.getmsg ();if ("401".equals(code)) {//todo refresh token when return 401} // Otherwise return response normallyCopy the code
  • Refresh token, which is a new interface; My current request is asynchronous and we want to intercept the response, so the interface for our refresh operation must be a synchronous request and we must get the result before we can proceed
Map<String, String> map = new ArrayMap<>();
        map.put("refreshToken", refreshToken); RequestBody = 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();Copy the code
  • After refreshing successfully, there are two operations: if 200 is returned, get the new token and re-request the interface that currently reports 401; if 410 is returned, it can also be 110.

  • Request again, we just need to get the last requestrequestBecause we intercepted the response in the current interceptorrequestThat’s what we reported earlier401Request, however, and then returns a response, in which you can also continue intercepting operations

@Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Request (builder.build()) request.builder Builder = request.newBuilder().addheader ()"Content-Type"."application/json; charset=UTF-8")
                .addHeader("Authorization", newToken); Proceed (must not be request, but builder.build()))return chain.proceed(builder.build());
    }
Copy the code

Ok, done

  • If you really thought it was over, you were naive, as I did;
  • I have several interfaces coming in concurrently, and it turns out that this doesn’t work, so what’s the effect?
  • The result would be that every interface reported 401 would refresh the token, and the loop would continue until 410, which is definitely not what I want.

  • Don’t panic ha, panic also want to solve the problem, in fact, it is not so complicated, the Internet search a wave of son, 5 minutes after the solution, add lock ah, really mechanism such as I [manual funny], specific is to refresh the token this code synchronization
synchronized (mContext) {
            Map<String, String> map = new ArrayMap<>();
            map.put("refreshToken", refreshToken); RequestBody = 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(); }Copy the code
  • But really want to still not solved ah, this is not at the same time to refreshtokenIn the end, every report401The request will still be refreshed

In fact, if we have refreshed the token, we can directly use the latest newToken to request the current interface again. We must keep the latest token global, but all our requests are asynchronous, so we can get each request. What does this mean? We can get the header, and then we have the token that expired before; If the two are compared, it means that the token has not been refreshed, so please refresh the token first. If the two are different, it means that the interface has been refreshed, you can directly request the latest newToken again. (Is a judgment will not post code [chuckles])

  • However, I have a problem here. I could not get the header through the following method. I debug found that the header was empty
String oldToken = request.header("Authorization");
String oldToken = request.headers().get("Authorization");
Copy the code
  • In fact, our header was added to the Builder, but I can’t seem to get it. Maybe it’s because of my posture (I took it sitting down, and I’ll try lying down later).
Request.Builder builder = request.newBuilder()
                .addHeader("Content-Type"."application/json; charset=UTF-8")
                .addHeader("Authorization", newToken);
Copy the code
  • Debug finds that there is also request in response, and it is not empty. This is sure to be successfully retrieved
String oldToken = response.request().headers().get("Authorization");
Copy the code
  • The result is the following
if ("401".equals(code)) {
            synchronized (mContext) {
                refreshToken = "Get the latest refreshToken"
                token = "Get the latest token"
                String oldToken = response.request().headers().get("Authorization"); /** * Whether the JWT in the current request is the same as the latest JWT in the localif* 2. If no, the token operation has been refreshedifRe-initiate the current request with the latest 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); // Set to global constant"One more thing you need to do here is save both of them locally, or they'll be gone the next time you log in."

                Request.Builder newBuilder = getBuilder(chain.request(), newToken);
                returngetNewResponse(chain, newBuilder); }}Copy the code
  • When concurrency comes, all requests are reported401When, there will only be the first time to refreshtokenAfter repeated and concurrent testing, this time it is really no problem.

Postscript: try more, look more, think more, the problem will be solved; As for the reference blog, I don’t know which one it is, thank you very much; If there is a mistake, don’t hesitate to give advice!