美文网首页
Android:给OHTTP添加Interceptor 拦截器

Android:给OHTTP添加Interceptor 拦截器

作者: 因为我的心 | 来源:发表于2020-10-26 15:23 被阅读0次

    一、前言:

    1:需求:

    我们需要对http的请求参数加密,还要对http的返回参数加密,这就需要用到Interceptor 拦截器了。

    2:OkHttp/Retrofit的实现

    现在说到网络框架,应该毫无疑问是Retrofit了。上面说的加密方案说到底还是要在网络请求框架内加上,怎么做入侵最小,怎么做最方便才是重点。

    1、坑定不能直接在接口调用层做加密,加参数,这样每个接口都要修改,这是不可能的。
    2、ConverterFactory处理,这也是网上可以搜到的很多文章的写法,但我觉得还是有入侵。而且有点麻烦。
    3、OkHttp添加拦截器,这种方法入侵最小(可以说没有),实现呢也非常优雅。

    二、代码实现:

    1、拦截器功能

    
    /**
     * @author by lyy on 2020/10/26.
     * okhttp  拦截器,主要功能为添加请求头
     */
    public class HttpInterceptorAll implements Interceptor {
    
        private static String requestValue;
        private static String requestHead;
        private static String reponseValue;
        DecryptBean decryptBean;
    
        public HttpInterceptorAll() {
            decryptBean = new DecryptBean();
        }
    
        @Override
        public Response intercept(Chain chain) throws IOException {
            Log.d("LUO", "-------------------------------------------拦截器前-------------------------------------------------");
            Request.Builder updateRequest = null;
            Request request = chain.request();
            Response response = null;
            //是否加密
            String appSecurity = BuildConfig.APP_SECURITY;
            //返回数据
            String responseDecrypt = "";
            String requestURI;
            try {
                String baseUrl = "";
                //请求数据
                String requestDecrypt = "";
                //请求token
                String token = SPUtils.getInstance(SPConstant.TOKEN).getString(SPConstant.TOKEN);
                //获取请求方式:
                String method = request.method();
                //请求连接
                requestURI = request.url().encodedPath();
                Log.d("LUO", "method:" + method);
                Log.d("LUO", "请求连接地址:" + requestURI);
    
                if ("true".equals(appSecurity)) {
                    //加密
                    //添加请求头
                    updateRequest = request.newBuilder()
                            .header("token", token)
                            .addHeader("device_id", BaseApplication.device)
                            .addHeader("deviceType", "Android")
                            .addHeader("compact", "1")
                            .addHeader("deviceSystem", DeviceUtils.getModel())
                            .addHeader("screenSize", ScreenUtils.getAppScreenWidth() + "x" + ScreenUtils.getAppScreenHeight())
                            .addHeader("version", BuildConfig.VERSION_NAME);
    
                    if ("GET".equals(method)) {
                        //参数后的值
                        String param = request.url().encodedQuery();
                        if (!TextUtils.isEmpty(param)) {
                            String encrypt = AESUtil.encrypt(param);
                            //get请求必须URLEncoded
                            baseUrl = BuildConfig.TEST_BASE_URL + requestURI + "?compact=" + toURLEncoded(encrypt);
                        } else {
                            baseUrl = BuildConfig.TEST_BASE_URL + requestURI;
                        }
    
                        Log.d("LUO", "请求参数:" + param + "\n" + baseUrl);
                    } else if ("POST".equals(method)) {
                        baseUrl = BuildConfig.TEST_BASE_URL + requestURI;
                        String param = getRequestInfo(request);
                        if (!TextUtils.isEmpty(param)) {
                            //body加密
                            String decrypt = AESUtil.encrypt(param);
                            decryptBean.setCompact(decrypt);
                            requestDecrypt = JSON.toJSONString(decryptBean);
                        } else {
                            requestDecrypt = "";
                        }
                        Log.d("LUO", "请求参数:" + requestDecrypt);
                        RequestBody requestBody = RequestBody.create(request.body().contentType(), requestDecrypt);
                        updateRequest.post(requestBody);
                    } else if ("PUT".equals(method)) {
                        baseUrl = BuildConfig.TEST_BASE_URL + requestURI;
                        String param = getRequestInfo(request);
                        if (TextUtils.isEmpty(param)) {
                            //body加密
                            String decrypt = AESUtil.encrypt(param);
                            decryptBean.setCompact(decrypt);
                            requestDecrypt = JSON.toJSONString(decryptBean);
                        } else {
                            requestDecrypt = "";
                        }
                        //Log.d("LUO", "请求参数:" + requestDecrypt);
                        RequestBody requestBody = RequestBody.create(request.body().contentType(), requestDecrypt);
                        updateRequest.put(requestBody);
                    }
                    updateRequest.url(baseUrl);
                    response = chain.proceed(updateRequest.build()).newBuilder().build();
                    //请求的
                    Log.d("LUO", "所有的请求头:" + updateRequest.build().headers() + "\n" + "请求参数:" + baseUrl);
                } else {
                    //未加密
                    //添加请求头
                    Request head = request.newBuilder().header("token", token)
                            .addHeader("device_id", BaseApplication.device)
                            .addHeader("deviceType", "Android")
                            .addHeader("deviceSystem", DeviceUtils.getModel())
                            .addHeader("screenSize", ScreenUtils.getAppScreenWidth() + "x" + ScreenUtils.getAppScreenHeight())
                            .addHeader("version", BuildConfig.VERSION_NAME)
                            .build();
                    String param = request.url().encodedQuery();
                    response = chain.proceed(head).newBuilder().build();
                    Log.d("LUO", "所有的请求头:" + head.headers() + "\n" + "请求参数:" + baseUrl + "?" + param);
                }
    
                /**
                 * 返回成功数据
                 */
                if (response.code() == 200) {
                    Log.d("LUO", "-------------------------------------------拦截器后-------------------------------------------------");
                    if ("true".equals(appSecurity)) {
                        //加密
                        //解析返回数据
                        String jsonStr = getResponseInfo(response);
                        Log.d("LUO", "解密之前返回数据:" + jsonStr);
                        if (!TextUtils.isEmpty(jsonStr) && jsonStr.contains("compact")) {
                            DecryptBean decryptBean = JSON.parseObject(jsonStr, DecryptBean.class);
                            String compact = decryptBean.getCompact();
                            try {
                                responseDecrypt = AESUtil.decrypt(compact);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            Log.d("LUO", "解密返回数据:" + responseDecrypt);
                        } else {
                            responseDecrypt = jsonStr;
                        }
    
                        //返回的
                        ResponseBody responseBody = ResponseBody.create(response.body().contentType(), responseDecrypt);
                        String json = getResponseInfo(response);
                        Log.d("LUO", "返回数据:" + json);
                        response = response.newBuilder().body(responseBody).build();
                    } else {
                        //未加密
                        Log.d("LUO", "token:" + token);
                        String str = getResponseInfo(response);
                        Log.d("LUO", "返回所有数据:" + str);
                    }
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            }
            return response;
        }
    
    
        /**
         * 打印请求消息
         *
         * @param request 请求的对象
         */
        private String getRequestInfo(Request request) {
            String str = "";
            if (request == null) {
                return str;
            }
            RequestBody requestBody = request.body();
            if (requestBody == null) {
                return str;
            }
            try {
                Buffer bufferedSink = new Buffer();
                requestBody.writeTo(bufferedSink);
                Charset charset = Charset.forName("utf-8");
                str = bufferedSink.readString(charset);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return str;
        }
    
        /**
         * 打印返回消息
         *
         * @param response 返回的对象
         */
        private String getResponseInfo(Response response) {
            String str = "";
            if (response == null || !response.isSuccessful()) {
                return str;
            }
            ResponseBody responseBody = response.body();
            long contentLength = responseBody.contentLength();
            BufferedSource source = responseBody.source();
            try {
                source.request(Long.MAX_VALUE); // Buffer the entire body.
            } catch (IOException e) {
                e.printStackTrace();
            }
            Buffer buffer = source.buffer();
            Charset charset = Charset.forName("utf-8");
            if (contentLength != 0) {
                str = buffer.clone().readString(charset);
            }
            return str;
        }
    
    
        /**
         * 对加密后的value数据url编码
         * URLEncoded编码
         * @param paramString
         * @return
         */
        public static String toURLEncoded(String paramString) {
            if (paramString == null || paramString.equals("")) {
                Log.d("LUO", "toURLEncoded error:" + paramString);
                return "";
            }
            try {
                String str = new String(paramString.getBytes(), "UTF-8");
                str = URLEncoder.encode(str, "UTF-8");
                return str;
            } catch (Exception localException) {
                Log.d("LUO", "toURLEncoded error:" + paramString, localException);
            }
    
            return "";
        }
    }
    
    

    2、添加请求头

    //然后OkHttp加入该拦截器:
    new OkHttpClient.Builder()
                    .addInterceptor(new HttpInterceptorAll())
                    ....
                    .build();
    

    3、AES加解密工具

    public class AESUtil {
        //算法
        private static final String ALGORITHM = "CBC";
        /**
         * 1.电码本模式(Electronic Codebook Book (ECB))不支持;
         * 2.密码分组链接模式(Cipher Block Chaining (CBC))默认的加密算法;
         * 3.计算器模式(Counter (CTR));
         * 4.密码反馈模式(Cipher FeedBack (CFB));
         * 5.输出反馈模式(Output FeedBack (OFB))。
         */
        // 指定加密的算法、工作模式和填充方式
        private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
        // 编码
        private static final String ENCODING = "UTF-8";
        // 密匙
        private static final String KEY = "nt38siidkkkkHsooo0";
        /***
         * 头信息:compact,不传则按照原来数据格式返回,加密则传1
         * aes:采用CBC加密模式
         * aes key:nt38vfVZhAHsBEd7
         * 偏移量:采用token前16位位偏移量,如token不存在或token长度不满16位则采用默认偏移量,默认偏移量:FseK2fyK7zgTjyUq
         */
        // 偏移量
        private static String OFFSET = "Fsjjjjdfdffpggq";
        private static String token;
    
        public AESUtil() {
            //请求token  e3fc31c7600a0a2c09e039e1e4f4c656_t
            OFFSET = "dhhdhdhgTjyUq";
        }
    
        /**
         * AES加密
         *
         * @param data
         * @return
         * @throws Exception
         */
        public static String encrypt(String data) throws Exception {
            token = SPUtils.getInstance(SPConstant.TOKEN).getString(SPConstant.TOKEN);
            if (!TextUtils.isEmpty(token)) {
                OFFSET = token.substring(0, 16);
            } else {
                OFFSET = "mmmoooTjyUxxxqll";
            }
    
            // 初始化cipher
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            //转化成JAVA的密钥格式
            SecretKeySpec skeySpec = new SecretKeySpec(KEY.getBytes("ASCII"), ALGORITHM);
            //使用CBC模式,需要一个向量iv,可增加加密算法的强度
            IvParameterSpec iv = new IvParameterSpec(OFFSET.getBytes());
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
            byte[] encrypted = cipher.doFinal(data.getBytes(ENCODING));
            //此处使用BASE64做转码。
    
            String result = new BASE64Encoder().encode(encrypted);
            return result;
        }
    
        /**
         * AES解密
         *
         * @param data
         * @return
         * @throws Exception
         */
        public static String decrypt(String data) throws Exception {
            Log.d("LUO", "初始化OFFSET:" + OFFSET);
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            SecretKeySpec skeySpec = new SecretKeySpec(KEY.getBytes("ASCII"), ALGORITHM);
            //使用CBC模式,需要一个向量iv,可增加加密算法的强度
            IvParameterSpec iv = new IvParameterSpec(OFFSET.getBytes());
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
            byte[] buffer = new BASE64Decoder().decodeBuffer(data);
            byte[] encrypted = cipher.doFinal(buffer);
            //此处使用BASE64做转码。
            String result = new String(encrypted, ENCODING);
            return result;
        }
    }
    

    三、功能解析:

    1、功能介绍:

      // 获取请求
    1、Request request = chain.request();
     //获取请求方式:
    2、String method = request.method();
      //请求连接
    3、String requestURI = request.url().encodedPath();
       //参数后的值
    4、String param = request.url().encodedQuery();
     //拼接url地址
    5、String baseUrl = BuildConfig.TEST_BASE_URL + requestURI + "?compact=" + encrypt;
    
    //添加请求头
    6、 Request.Builder updateRequest = request.newBuilder()
                            .header("token", token)
                            .addHeader("device_id", BaseApplication.device)
                            .addHeader("deviceType", "Android")
                            .addHeader("compact", "1")
                            .addHeader("deviceSystem", DeviceUtils.getModel())
                            .addHeader("screenSize", ScreenUtils.getAppScreenWidth() + "x" + ScreenUtils.getAppScreenHeight())
                            .addHeader("version", BuildConfig.VERSION_NAME);
    
     //POST请求参数
    7、RequestBody requestBody = RequestBody.create(request.body().contentType(), “加密后数据”);
                        updateRequest.post(requestBody);
    
     //添加requestBody参数
    8、updateRequest.post(requestBody);
    
     //添加请求的url
    9、updateRequest.url(baseUrl);
    
    //生成请求的Response
    10、Response  response = chain.proceed(updateRequest.build()).newBuilder().build();
    
    //等于200是请求返回
    11、response.code() == 200
    
    // 请求返回的参数
    12、ResponseBody responseBody = ResponseBody.create(response.body().contentType(), “解密后数据”);
    // 添加请求返回的参数
    13、 Response  response = response.newBuilder().body(responseBody).build();
    
    //正常不加密的请求头
    14、Request head = request.newBuilder().header("token", token)
                            .addHeader("device_id", BaseApplication.device)
                            .addHeader("deviceType", "Android")
                            .addHeader("deviceSystem", DeviceUtils.getModel())
                            .addHeader("screenSize", ScreenUtils.getAppScreenWidth() + "x" + ScreenUtils.getAppScreenHeight())
                            .addHeader("version", BuildConfig.VERSION_NAME)
                            .build();
                    String param = request.url().encodedQuery();
                    Response response = chain.proceed(head).newBuilder().build();
    
    //get请求参数加密,必须url的参数编码,否则服务器解析失败(URLEncode主要是把一些特殊字符转换成转移字符)
    15、   String   baseUrl = BuildConfig.TEST_BASE_URL + requestURI + "?compact=" + toURLEncoded(encrypt);
    
    //编码方法
    16、  /**
         * 对加密后的value数据url编码
         * URLEncoded编码
         * @param paramString
         * @return
         */
        public static String toURLEncoded(String paramString) {
            if (paramString == null || paramString.equals("")) {
                Log.d("LUO", "toURLEncoded error:" + paramString);
                return "";
            }
            try {
                String str = new String(paramString.getBytes(), "UTF-8");
                str = URLEncoder.encode(str, "UTF-8");
                return str;
            } catch (Exception localException) {
                Log.d("LUO", "toURLEncoded error:" + paramString, localException);
            }
    
            return "";
        }
    
    

    2、主要注意点:

    1、和接口无关的新加的数据放在请求头里。
    2、该close的要close,不然会内存泄漏。
    3、新旧Request和Response要区分好,新的要替换旧的去传递或返回。
    4、要对response.code()做处理,只有在和后台约定好的返回码下才走解密的逻辑,具体看自己的需求,不一定都是200。

    3、特别需要注意的地方:

    //这个方法只能调一次;多次调用会出现多次请求
    Response response = chain.proceed(request);
    

    4、其它参考:

    ----->先定义一个拦截器的实现:
    public class DataEncryptInterceptor implements Interceptor {
            @Override
            public Response intercept(Chain chain) throws IOException {
                //请求
                Request request = chain.request();
                RequestBody oldRequestBody = request.body();
                Buffer requestBuffer = new Buffer();
                oldRequestBody.writeTo(requestBuffer);
                String oldBodyStr = requestBuffer.readUtf8();
                requestBuffer.close();
                MediaType mediaType = MediaType.parse("text/plain; charset=utf-8");
                //生成随机AES密钥并用serverPublicKey进行RSA加密
                SecretKeySpec appAESKeySpec = EncryptUtils.generateAESKey(256);
                String appAESKeyStr = EncryptUtils.covertAESKey2String(appAESKeySpec);
                String appEncryptedKey = RSAUtils.encryptDataString(appAESKeyStr, serverPublicKey);
                //计算body 哈希 并使用app私钥RSA签名
                String appSignature = RSAUtils.signature(oldBodyStr, appPrivateKey);
                //随机AES密钥加密oldBodyStr
                String newBodyStr = EncryptUtils.encryptAES(appAESKeySpec, oldBodyStr);
                RequestBody newBody = RequestBody.create(mediaType, newBodyStr);
                //构造新的request
                request = request.newBuilder()
                        .header("Content-Type", newBody.contentType().toString())
                        .header("Content-Length", String.valueOf(newBody.contentLength()))
                        .method(request.method(), newBody)
                        .header("appEncryptedKey", appEncryptedKey)
                        .header("appSignature", appSignature)
                        .header("appPublicKey", appPublicKeyStr)
                        .build();
                //响应
                Response response = chain.proceed(request);
                if (response.code() == 200) {//只有约定的返回码才经过加密,才需要走解密的逻辑
                    //获取响应头
                    String serverEncryptedKey = response.header("serverEncryptedKey");
                    //用app的RSA私钥解密AES加密密钥
                    String serverDecryptedKey = RSAUtils.decryptDataString(serverEncryptedKey, appPrivateKey);
                    SecretKeySpec serverAESKeySpec = EncryptUtils.covertString2AESKey(serverDecryptedKey);
                    //用AES密钥解密oldResponseBodyStr
                    ResponseBody oldResponseBody = response.body();
                    String oldResponseBodyStr = oldResponseBody.string();
                    String newResponseBodyStr = EncryptUtils.decryptAES(serverAESKeySpec, oldResponseBodyStr);
                    oldResponseBody.close();
                    //构造新的response
                    ResponseBody newResponseBody = ResponseBody.create(mediaType, newResponseBodyStr);
                    response = response.newBuilder().body(newResponseBody).build();
                }
                response.close();
                //返回
                return response;
            }
        }
     
    ----->然后OkHttp加入该拦截器:
    new OkHttpClient.Builder()
                    .addInterceptor(new DataEncryptInterceptor())
                    ....
                    .build();
     
    ----->这样就搞定了。
    

    参考地址:https://www.waitalone.cn/mobilesecurity/okhttp.html

    相关文章

      网友评论

          本文标题:Android:给OHTTP添加Interceptor 拦截器

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