美文网首页Android笔记本springbootJava开发
接口请求身份认证的Token和RefreshToken的解决方案

接口请求身份认证的Token和RefreshToken的解决方案

作者: 猫小萌的哥哥 | 来源:发表于2019-04-18 18:22 被阅读440次

    前言

    最近公司在改造接口的请求的验证,之前是登陆后返回一个token,在请求的时候动态添加到header中,以此来验证身份,当返回401直接去重新登录;现在登录返回tokenrefreshToken两个参数,拿token去添加header,当返回401时并不直接去登录而是拿refreshToken去请求一个接口,刷新得到新的tokenrefreshToken,拿到新的token再去请求当前返回401的接口,如果此时返回410则是真正的过期才需要去登录。

    准备工作

    • 首先,因为之前用了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真正的过期了,直接去重新登录。

    • 重新请求,我们此时只需要拿到上次请求的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的去重新请求就好了。(就是一个判断就不贴代码了【偷笑】)

    • 但是这里我出现了一个问题,通过下面这个方法并拿不到,我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 oldJwt = response.request().headers().get("Authorization");
    
    • 于是有了下面的结果
    if ("401".equals(code)) {
                synchronized (mContext) {
                    refreshToken = "获取最新的refreshToken"
                    token = "获取最新的token"
                    String oldToken = response.request().headers().get("Authorization");
    
                    /**
                     * 当前请求中的jwt和本地最新的是否一样:
                     * 1、一样则说明没有进行刷新token操作不进入此 if
                     * 2、不一样则说明已经刷新过token操作了,进入此 if 拿最新的jwt直接重新发起当前的请求
                     */
                    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,达到想要的效果了经过反复以及并发的测试,这回算是真的没问题了。

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

    相关文章

      网友评论

        本文标题:接口请求身份认证的Token和RefreshToken的解决方案

        本文链接:https://www.haomeiwen.com/subject/dnqtgqtx.html