Android:给OHTTP添加Interceptor 拦截器

一、前言:

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 拦截器)