接口请求身份认证的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,达到想要的效果了通过反复以及并发的测试,这回算是真的没问题了。

后记:多试试、多看看、多想一想,问题总会解决的;关于参考博客,我也不知道是哪一篇了,十分感谢;若有纰漏,不吝赐教!

相关文章
相关标签/搜索