欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

RequestBodyAdvice和ResponseBodyAdvice使用完成入参解密和返回加密

程序员文章站 2022-07-12 19:41:48
...

模拟项目中使用RequestBodyAdvice对前端传入的数据进行解密(入参),请求成功之后使用ResponseBodyAdvice对返回值进行加密处理

注意点:分别需要实现接口

RequestBodyAdvice 和 ResponseBodyAdvice,需要配合注解@ControllerAdvice使用

特别需要注意的是,针对RequestBodyAdvice仅作用在请求参数有注解@RequestBody的,同样的ResponseBodyAdvice仅对有注解@ResponseBody生效

 

下面的代码演示:

新建requestBody和responseBody拦截类:

package com.wm.mi.config;

import com.wm.mi.exception.MyException;
import com.wm.mi.util.HttpInputMessageUtil;
import com.wm.mi.util.Md5Tool;
import com.wm.mi.util.RequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.List;

/***
 * @ClassName: DecodeRequestBody
 * @Description: 请求解密  注意:RequestBodyAdvice仅针对打上@RequestBody的注解生效
 * @Author: wm_yu
 * @Create_time: 14:43 2019-11-11
 */
@Slf4j
@Component
@ControllerAdvice("com.wm.mi.controller")
public class DecodeRequestBody implements RequestBodyAdvice {

    @Value("${enctry.secret}")
    private String SECRET;

    private static final String SECRET_KEY = "SECRET_KEY";

    /**
     *  设置条件,这个条件为true才会执行下面的beforeBodyRead方法
     * @param methodParameter
     * @param type
     * @param aClass
     * @return
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        List<String> headerList = request.getHeaders().get(SECRET_KEY);
        if(CollectionUtils.isEmpty(headerList) || StringUtils.isEmpty(headerList.get(0)) || !SECRET.equals(headerList.get(0))){
            throw new MyException("request header no access key....");
        }
        InputStream inputStream = request.getBody();
        String requestBodyStr = RequestUtil.getRequestBodyStr(inputStream);
        log.info("start decode request body:{}",requestBodyStr);
        if(StringUtils.isEmpty(requestBodyStr)){
            return request;
        }
        String decodeStr = null;
        try {
            decodeStr = Md5Tool.md5Decode(requestBodyStr, SECRET);
            log.info("end decode request body:{}",decodeStr);
        } catch (Exception e) {
            throw new MyException("decode request body exception....");
        }
        return HttpInputMessageUtil.builder().headers(request.getHeaders()).body(new ByteArrayInputStream(decodeStr.getBytes("UTF-8"))).build();
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return body;
    }

    /**
     * 传入的json是空值的时候,进入这个方法
     * @param o
     * @param httpInputMessage
     * @param methodParameter
     * @param type
     * @param aClass
     * @return
     */
    @Override
    public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        log.info("input request body be null......");
        return o;
    }
}

 

 

package com.wm.mi.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.wm.mi.exception.MyException;
import com.wm.mi.util.Md5Tool;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/***
 * @ClassName: EncodeResponseBody
 * @Description: 返回加密   注意ResponseBodyAdvice仅针对打上@ResponseBody注解的返回值生效
 * @Author: wm_yu
 * @Create_time: 14:43 2019-11-11
 */
@Slf4j
@Component
@ControllerAdvice("com.wm.mi.controller")
public class EncodeResponseBody implements ResponseBodyAdvice {

    @Value("${enctry.secret}")
    private String SECRET;

    /**
     * 设置条件,这个条件为true才会执行下面的beforeBodyWrite方法
     * @param methodParameter
     * @param aClass
     * @return
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        return true;
    }

    /**
     * 返回值加密处理
     * @param body
     * @param methodParameter
     * @param mediaType
     * @param aClass
     * @param serverHttpRequest
     * @param serverHttpResponse
     * @return
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        //禁止fastjson转换循环引用
        String bodyStr = JSON.toJSONString(body, SerializerFeature.DisableCircularReferenceDetect);
        log.info("start encode response:{}", JSON.toJSONString(body));
        String encodeStr = null;
        try {
            encodeStr = Md5Tool.md5Encode(bodyStr, SECRET);
            log.info("end encode response:{}",encodeStr);
        } catch (Exception e) {
            throw new MyException("encode response body exception...");
        }
        return encodeStr;
    }
}

Yml中自定义的一个 常量:

enctry:
  secret: d5416a341766390368ab75d220a6c051

 

使用到的MD加密解密工具类:

 

 

package com.wm.mi.util;

/***
 * @ClassName: Md5Tool
 * @Description: 简单MD5加密解密工具类
 * @Author: wm_yu
 * @Create_time: 14:53 2019-11-11
 */

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;

/**
 * @class_name: MD5Tool
 * @description:
 * @author: wm_yu
 * @create: 2019/09/12
 **/
public class Md5Tool {

    private final static Logger log = LoggerFactory.getLogger(Md5Tool.class);
    /**
     * 向量(同时拥有向量和密匙才能解密),此向量必须是8byte,多少都报错
     */
    private final static byte[] DESIV = new byte[] { 0x22, 0x54, 0x36, 110, 0x40, (byte) 0xac, (byte) 0xad, (byte) 0xdf };
    /**
     * 加密算法的参数接口
     */
    private static AlgorithmParameterSpec iv = null;
    private static Key key = null;
    private static String charset = "utf-8";

   private static void init(String SECRET_KEY){
       try {
           // 设置**参数
           DESKeySpec keySpec = new DESKeySpec(SECRET_KEY.getBytes(charset));
           // 设置向量
           iv = new IvParameterSpec(DESIV);
           // 获得**工厂
           SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
           // 得到**对象
           key = keyFactory.generateSecret(keySpec);
       } catch (Exception e) {
           e.printStackTrace();
       }
   }



    /**
     * 加密
     * @param data
     * @return
     * @throws Exception
     */
    public static String md5Encode(String data,String SECRET_KEY) throws Exception {
        init(SECRET_KEY);
        // 得到加密对象Cipher
        Cipher enCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
        // 设置工作模式为加密模式,给出**和向量
        enCipher.init(Cipher.ENCRYPT_MODE, key, iv);
        byte[] pasByte = enCipher.doFinal(data.getBytes(charset));
        BASE64Encoder base64Encoder = new BASE64Encoder();
        return base64Encoder.encode(pasByte);
    }

    /**
     * 解密
     * @param data
     * @return
     * @throws Exception
     */
    public static String md5Decode(String data,String SECRET_KEY) throws Exception {
        init(SECRET_KEY);
        Cipher deCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
        deCipher.init(Cipher.DECRYPT_MODE, key, iv);
        BASE64Decoder base64Decoder = new BASE64Decoder();
        //此处注意doFinal()的参数的位数必须是8的倍数,否则会报错(通过encode加密的字符串读出来都是8的倍数位,但写入文件再读出来,就可能因为读取的方式的问题,导致最后此处的doFinal()的参数的位数不是8的倍数)
        //此处必须用base64Decoder,若用data。getBytes()则获取的字符串的byte数组的个数极可能不是8的倍数,而且不与上面的BASE64Encoder对应(即使解密不报错也不会得到正确结果)
        byte[] pasByte = deCipher.doFinal(base64Decoder.decodeBuffer(data));
        return new String(pasByte, charset);
    }

    /**
     * 获取MD5的值,可用于对比校验
     * @param sourceStr
     * @return
     */
    private static String getMD5Value(String sourceStr) {
        String result = "";
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(sourceStr.getBytes());
            byte b[] = md.digest();
            int i;
            StringBuffer buf = new StringBuffer("");
            for (int offset = 0; offset < b.length; offset++) {
                i = b[offset];
                if (i < 0){ i += 256;}
                if (i < 16){buf.append("0");}
                buf.append(Integer.toHexString(i));
            }
            result = buf.toString();
        } catch (NoSuchAlgorithmException e) {
        }
        return result;
    }

    public static void main(String[] args) {
        try {
            String secret_key = "d5416a341766390368ab75d220a6c051";
            String source = "name:yu";
            String value = md5Encode(source,secret_key);
            String decode = md5Decode(value,secret_key);
            System.out.println("原始数据:" + source + "----加密后的数据:" + value + "-----解密后的数据:" + decode);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }



}

构建request处理之后的返回数据类:

package com.wm.mi.util;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;

import java.io.IOException;
import java.io.InputStream;

/***
 * @ClassName: HttpInputMessageUtil
 * @Description:
 * @Author: wm_yu
 * @Create_time: 15:32 2019-11-11
 */
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Accessors(fluent = true)
public class HttpInputMessageUtil implements HttpInputMessage {

    private HttpHeaders headers;
    private InputStream body;

    @Override
    public InputStream getBody() throws IOException {
        return body;
    }

    @Override
    public HttpHeaders getHeaders() {
        return headers;
    }

}

获取request中的数据工具类:

package com.wm.mi.util;

import org.springframework.util.ObjectUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/***
 * @ClassName: RequestUtil
 * @Description:
 * @Author: wm_yu
 * @Create_time: 15:16 2019-11-11
 */
public class RequestUtil {


    /**
     * reuqest body流数据转换为String
     * @param inputStream
     * @return
     * @throws IOException
     */
    public static String getRequestBodyStr(InputStream inputStream) throws IOException {
        StringBuilder builder = new StringBuilder();
        if (!ObjectUtils.isEmpty(inputStream)) {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            char[] charBuffer = new char[128];
            int bytesRead = -1;
            while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                builder.append(charBuffer, 0, bytesRead);
            }
        }else {
            builder.append("");
        }
        return builder.toString();

    }


}

自定义一个异常类:

package com.wm.mi.exception;

import lombok.AllArgsConstructor;

/***
 * @ClassName: MyException
 * @Description: 自定义异常
 * @Author: wm_yu
 * @Create_time: 15:14 2019-11-11
 */
@AllArgsConstructor
public class MyException extends RuntimeException{

    private Integer errorCode;
    public MyException() {
    }

    public MyException(String message) {
        super(message);
    }

    public MyException(Integer errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }
    public Integer getErrorCode() {
        return this.errorCode;
    }

    public void setErrorCode(Integer errorCode) {
        this.errorCode = errorCode;
    }

}

新建cotroller测试类:

package com.wm.mi.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/***
 * @ClassName: HelloController
 * @Description:
 * @Author: wm_yu
 * @Create_time: 16:35 2019-11-11
 */
@RestController
@RequestMapping("/yu")
public class HelloController {

    @GetMapping("/hello")
    public String hello(@RequestBody String name){
        return String.format("hell0,%s",name);
    }
}

 

先使用工具类,构建一个加密之后的请求参数,如下

RequestBodyAdvice和ResponseBodyAdvice使用完成入参解密和返回加密

RequestBodyAdvice和ResponseBodyAdvice使用完成入参解密和返回加密

模拟请求:

RequestBodyAdvice和ResponseBodyAdvice使用完成入参解密和返回加密

结果如下:RequestBodyAdvice和ResponseBodyAdvice使用完成入参解密和返回加密

 

RequestBodyAdvice和ResponseBodyAdvice使用完成入参解密和返回加密