Retrofit实现token失效的处理方案

前言:我在Android项目中用Retrofit处理token失效,经过不断调试,最终实现了功能。因此,我简单的总结了一下token失效的处理方案,希望对大家有所帮助!

思路:

1.token验证是为了提高app的安全性,需求规定access_token的有效期为1小时,refresh_Token的有效期为7天;
2.如果access_token1个小时后过期了,服务器会返回400或者401,此时客户端要根据刷新access_token的retrofit接口去重新请求新的access_token;
3.如果refresh_Token7天后也过期了,则要求跳到登录页面。

实现步骤:

1.根据刷新的access_token的retrofit接口去重新请求新的access_token

 if (response.code() == 400 || response.code() == 401) {
                    if (!path.contains("/touch")) {//代表不是刷新access_token
                        //根据后台提供的接口获取新的access_token,比如/api/member/{uuid}/touch?refresh_token=xxxx,如果接口不同,这里需要改动。
                        String membership_uuid = SharedPreUtil.getString(Global.mContext, "membership_uuid", "");//获取membership_uuid
                        String refresh_token = SharedPreUtil.getString(Global.mContext, "refresh_token", "");//获取refresh_token
                        retrofit2.Response<JsonObject> execute = RetrofitManager.getInstance().getHttpService().getNewToken(membership_uuid, refresh_token).execute();
                        if (execute.code() == 200) {//200代表refresh_token的时间是有效的
                            JsonObject body = execute.body();
                            //解析这个jsonBoject,得到新的access_token
                            String new_token = body.get("access_token").getAsString();
                            //保存 access_token
                            SharedPreUtil.saveString(Global.mContext, "access_token", new_token);
                            LogUtil.e("----------", new_token);
                            //重新发起请求
                            Request newRequest = request.newBuilder()
                                    .removeHeader("x-access-token")   //移除旧的token
                                    .header("x-access-token", new_token)  //添加新的token
                                    .build();

                            return chain.proceed(newRequest);//重新发起请求,此时是新的token
                        } else {//代表refresh_token过期
                            //要求用户直接登录
                        }
                    } else {
                        return response;
                    }
                } else {
                    //此时没有过期
                    Log.d("============", "intercept: token ------------");
                }

2.需要拦截的接口

 if (isMembers(path)) {//凡是接口中包含members都加入拦截器
            //加拦截器,x-access-token是access_token的key,这个要问清楚后台
            request = request.newBuilder().addHeader("x-access-token", SharedPreUtil.getString(Global.mContext, "access_token", "")).build();

 }

 /**
  *判断刷新token连接,直接返回,不走下面代码,避免死循环
  */
if (request.url().toString().contains("/tokens/") && request.method() == "GET"){
            return  chain.proceed(request) ;
 }

3.接口中包含members的方法

public boolean isMembers(String path) {
        if (path.contains("members") && !path.contains("auth")) {
            return true;
        }
        return false;
 }

4.token拦截器的完整代码

public class TokenInterceptor implements Interceptor {
    private static final Charset UTF8 = Charset.forName("UTF-8");

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        String path = request.url().encodedPath();
        
        if (isMembers(path)){ //凡是接口中包含Members都添加到拦截器,如果需求不同,这里需要改动。
            //加拦截器,x-access-token是access_token的key,这个要问清楚后台
            request = request.newBuilder().addHeader("x-access-token", SharedPreUtil.getString(Global.mContext, "access_token", "")).build();
        }
        /**
         *判断刷新token连接,直接返回,不走下面代码,避免死循环。
         */
        if (request.url().toString().contains("/tokens/") && request.method() == "GET"){
            return  chain.proceed(request) ;
        }
        //拦截了响应体
        Response response = chain.proceed(request);
        ResponseBody responseBody = response.body();
        long contentLength = responseBody.contentLength();
        if (!HttpHeaders.hasBody(response)) {
            //END HTTP
        } else if (bodyEncoded(response.headers())) {
            //HTTP (encoded body omitted)
        } else {
            BufferedSource source = responseBody.source();
            source.request(Long.MAX_VALUE); // Buffer the entire body.
            Buffer buffer = source.buffer();
            Charset charset = UTF8;
            MediaType contentType = responseBody.contentType();
            if (contentType != null) {
                try {
                    charset = contentType.charset(UTF8);
                } catch (UnsupportedCharsetException e) {
                    return response;
                }
            }
            if (!isPlaintext(buffer)) {
                return response;
            }
            if (contentLength != 0) {
                //获取到response的body的string字符串
                String result = buffer.clone().readString(charset);
                //当状态码返回的是400或者401,即代表过期
                if (response.code() == 400 || response.code() == 401) {
                    if (!path.contains("/touch")){//代表不是刷新access_token
                        //获取新的access_token
                        String membership_uuid = SharedPreUtil.getString(Global.mContext, "membership_uuid", "");//获取membership_uuid
                        String refresh_token = SharedPreUtil.getString(Global.mContext, "refresh_token", "");//获取refresh_token
                        retrofit2.Response<JsonObject> execute = RetrofitManager.getInstance().getHttpService().getNewToken(membership_uuid, refresh_token).execute();
                        if (execute.code()==200) {
                            JsonObject body = execute.body();
                            //解析这个JsonObject,得到新的access_token
                            String new_token = body.get("access_token").getAsString();
                            //保存 access_token,覆盖旧的accesss_token
                            SharedPreUtil.saveString(Global.mContext, "access_token", new_token);
                            LogUtil.e("----------", new_token);
                            //重新发起请求
                            Request newRequest = request.newBuilder()
                                    .removeHeader("x-access-token")   //移除旧的token
                                    .header("x-access-token", new_token)  //添加新的token
                                    .build();

                            return chain.proceed(newRequest);//重新发起请求,此时是新的token
                        }else{
                            //要求用户直接登录
                             GotoLoginActivity(Global.mContext);
                        }
                    }else {
                        return response;
                    }
                } else {
                    //此时没有过期
                    Log.d("============", "intercept: token ------------");
                }
            }
        }
        return response;
    }

   //要求用户直接登录
    private void GotoLoginActivity(Context context) {
        AppManager.getInstance().finishOthersActivity(LoginActivity.class);
        Intent intent = new Intent(context, LoginActivity.class);
        context.startActivity(intent);
    }

    static boolean isPlaintext(Buffer buffer) throws EOFException {
        try {
            Buffer prefix = new Buffer();
            long byteCount = buffer.size() < 64 ? buffer.size() : 64;
            buffer.copyTo(prefix, 0, byteCount);
            for (int i = 0; i < 16; i++) {
                if (prefix.exhausted()) {
                    break;
                }
                int codePoint = prefix.readUtf8CodePoint();
                if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
                    return false;
                }
            }
            return true;
        } catch (EOFException e) {
            return false; // Truncated UTF-8 sequence.
        }
    }

    private boolean bodyEncoded(Headers headers) {
        String contentEncoding = headers.get("Content-Encoding");
        return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity");
    }

    //项目中所有包含members的接口
    public boolean isMembers(String path){
        if (path.contains("members") && !path.contains("auth")){
            return true;
        }
        return false;
    }
}

5.在RetrofitManager中添加token拦截器

public class RetrofitManager {
      ...
     private void initRetrofit() {
        //加入token拦截器
        client.interceptors().add(new TokenInterceptor());
         ...
     }
}

6.总结:token验证的好处就是,当用户在下次发送请求的时候,不用再携带用户名和密码,只需要携带Token和相应请求需要带的参数即可。这样可以减轻服务器的负担,也提高安全性。

猜你喜欢

转载自my.oschina.net/u/3286162/blog/1529637