使用注解+RequestBodyAdvice实现http请求内容加解密方式

注解主要用来指定那些需要加解密的controller方法

实现比较简单

@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface SecretAnnotation {    boolean encode() default false;    boolean decode() default false;}

使用时添加注解在controller的方法上

    @PostMapping("/preview")    @SecretAnnotation(decode = true)    public ResponseVO<ContractSignVO> previewContract(@RequestBody FillContractDTO fillContractDTO)  {        return contractSignService.previewContract(fillContractDTO);    }

请求数据由二进制流转为类对象数据,对于加密过的数据,需要在二进制流被处理之前进行解密,否则在转为类对象时会因为数据格式不匹配而报错。

因此使用RequestBodyAdvice的beforeBodyRead方法来处理。

@Slf4j@RestControllerAdvicepublic class MyRequestControllerAdvice implements RequestBodyAdvice {    @Override    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {        return methodParameter.hasParameterAnnotation(RequestBody.class);    }    @Override    public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {        return o;    }    @Autowired    private MySecretUtil mySecretUtil;    @Override    public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {        if (methodParameter.getMethod().isAnnotationPresent(SecretAnnotation.class)) {            SecretAnnotation secretAnnotation = methodParameter.getMethod().getAnnotation(SecretAnnotation.class);            if (secretAnnotation.decode()) {                return new HttpInputMessage() {                    @Override                    public InputStream getBody() throws IOException {                        List<String> appIdList = httpInputMessage.getHeaders().get("appId");                        if (appIdList.isEmpty()){                            throw new RuntimeException("请求头缺少appID");                        }                        String appId = appIdList.get(0);                        String bodyStr = IOUtils.toString(httpInputMessage.getBody(),"utf-8");                        bodyStr = mySecretUtil.decode(bodyStr,appId);                        return  IOUtils.toInputStream(bodyStr,"utf-8");                    }                    @Override                    public HttpHeaders getHeaders() {                        return httpInputMessage.getHeaders();                    }                };            }        }        return httpInputMessage;    }    @Override    public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {        return o;    }}

mySecretUtil.decode(bodyStr,appId)的内容是,通过请求头中的AppID去数据库中查找对于的秘钥,之后进行解密,返回解密后的字符串。

再通过common.io包中提供的工具类IOUtils将字符串转为inputstream流,替换HttpInputMessage,返回一个body数据为解密后的二进制流的HttpInputMessage。

Stringboot RequestBodyAdvice接口如何实现请求响应加解密

在实际项目中,我们常常需要在请求前后进行一些操作,比如:参数解密/返回结果加密,打印请求参数和返回结果的日志等。这些与业务无关的东西,我们不希望写在controller方法中,造成代码重复可读性变差。这里,我们讲讲使用@ControllerAdvice和RequestBodyAdvice、ResponseBodyAdvice来对请求前后进行处理(本质上就是AOP),来实现日志记录每一个请求的参数和返回结果。

1.加解密工具类

package com.linkus.common.utils;import java.security.Key;import java.security.NoSuchAlgorithmException;import java.security.NoSuchProviderException;import java.security.Security;import javax.annotation.PostConstruct;import javax.crypto.Cipher;import javax.crypto.NoSuchPaddingException;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;import org.bouncycastle.jce.provider.BouncyCastleProvider;import org.bouncycastle.util.encoders.Hex;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;@Componentpublic class Aes {    /**     *     * @author ngh     * AES128 算法     *     * CBC 模式     *     * PKCS7Padding 填充模式     *     * CBC模式需要添加偏移量参数iv,必须16位     * 密钥 sessionKey,必须16位     *     * 介于java 不支持PKCS7Padding,只支持PKCS5Padding 但是PKCS7Padding 和 PKCS5Padding 没有什么区别     * 要实现在java端用PKCS7Padding填充,需要用到bouncycastle组件来实现     */    private String sessionKey="加解密密钥";    // 偏移量 16位    private static  String iv="偏移量";    // 算法名称    final String KEY_ALGORITHM = "AES";    // 加解密算法/模式/填充方式    final String algorithmStr = "AES/CBC/PKCS7Padding";    // 加解密 密钥 16位    byte[] ivByte;    byte[] keybytes;    private Key key;    private Cipher cipher;    boolean isInited = false;    public void init() {        // 如果密钥不足16位,那么就补足.  这个if 中的内容很重要        keybytes = iv.getBytes();        ivByte = iv.getBytes();        Security.addProvider(new BouncyCastleProvider());        // 转化成JAVA的密钥格式        key = new SecretKeySpec(keybytes, KEY_ALGORITHM);        try {            // 初始化cipher            cipher = Cipher.getInstance(algorithmStr, "BC");        } catch (NoSuchAlgorithmException e) {            // TODO Auto-generated catch block            e.printStackTrace();        } catch (NoSuchPaddingException e) {            // TODO Auto-generated catch block            e.printStackTrace();        } catch (NoSuchProviderException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }    /**     * 加密方法     *     * @param content     *            要加密的字符串     *            加密密钥     * @return     */    public String encrypt(String content) {        byte[] encryptedText = null;        byte[] contentByte = content.getBytes();        init();        try {            cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(ivByte));            encryptedText = cipher.doFinal(contentByte);        } catch (Exception e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        return new String(Hex.encode(encryptedText));    }    /**     * 解密方法     *     * @param encryptedData     *            要解密的字符串     *            解密密钥     * @return     */    public String decrypt(String encryptedData) {        byte[] encryptedText = null;        byte[] encryptedDataByte = Hex.decode(encryptedData);        init();        try {            cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivByte));            encryptedText = cipher.doFinal(encryptedDataByte);        } catch (Exception e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        return new String(encryptedText);    }    public static void main(String[] args) {        Aes aes = new Aes();        String a="{\n" +                "\"distance\":\"1000\",\n" +                "\"longitude\":\"28.206471\",\n" +                "\"latitude\":\"112.941301\"\n" +                "}";        //加密字符串        //String content = "孟飞快跑";        // System.out.println("加密前的:" + content);//        System.out.println("加密密钥:" + new String(keybytes));        // 加密方法        String enc = aes.encrypt(a);        System.out.println("加密后的内容:" + enc);        String dec="";        // 解密方法        try {            dec = aes.decrypt(enc);        } catch (Exception e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        System.out.println("解密后的内容:" + dec);    }}

2.请求解密

前端页面传过来的是密文,我们需要在Controller获取请求之前对密文解密然后传给Controller

package com.linkus.common.filter;import com.alibaba.fastjson.JSON;import com.linkus.common.constant.KPlatResponseCode;import com.linkus.common.exception.CustomException;import com.linkus.common.exception.JTransException;import com.linkus.common.service.util.MyHttpInputMessage;import com.linkus.common.utils.Aes;import com.linkus.common.utils.http.HttpHelper;import lombok.extern.slf4j.Slf4j;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.core.MethodParameter;import org.springframework.http.HttpInputMessage;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.lang.Nullable;import org.springframework.stereotype.Component;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;import javax.servlet.http.HttpServletRequest;import java.io.*;import java.lang.reflect.Type;/** * 请求参数 解密操作 * * @Author: Java碎碎念 * @Date: 2019/10/24 21:31 * */@Component//可以配置指定需要解密的包,支持多个@ControllerAdvice(basePackages = {"com.linkus.project"})@Slf4jpublic class DecryptRequestBodyAdvice implements RequestBodyAdvice {    Logger log = LoggerFactory.getLogger(getClass());    Aes aes=new Aes();    @Override    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {    //true开启功能,false关闭这个功能        return true;    }//在读取请求之前做处理    @Override    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> selectedConverterType) throws IOException {        //获取请求数据        String string = "";        BufferedReader bufferedReader = null;        InputStream inputStream = inputMessage.getBody();            //这个request其实就是入参 可以从这里获取流            //入参放在HttpInputMessage里面  这个方法的返回值也是HttpInputMessage            try {            string=getRequestBodyStr(inputStream,bufferedReader);        } finally {            if (bufferedReader != null) {                try {                    bufferedReader.close();                } catch (IOException ex) {                    throw ex;                }            }        }        /*****************进行解密start*******************/        String decode = null;        if(HttpHelper.isEncrypted(inputMessage.getHeaders())){        try {            //            //解密操作            //Map<String,String> dataMap = (Map)body;            //log.info("接收到原始请求数据={}", string);            // inputData 为待加解密的数据源//解密            decode= aes.decrypt(string);            //log.info("解密后数据={}",decode);        } catch (Exception e ) {            log.error("加解密错误:",e);           throw  new CustomException(KPlatResponseCode.MSG_DECRYPT_TIMEOUT,KPlatResponseCode.CD_DECRYPT_TIMEOUT);        }        //把数据放到我们封装的对象中        }else{            decode = string;        }       // log.info("接收到请求数据={}", decode);//        log.info("接口请求地址{}",((HttpServletRequest)inputMessage).getRequestURI());        return new MyHttpInputMessage(inputMessage.getHeaders(), new ByteArrayInputStream(decode.getBytes("UTF-8")));    }    @Override    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {        return body;    }    @Override    public Object handleEmptyBody(@Nullable Object var1, HttpInputMessage var2, MethodParameter var3, Type var4, Class<? extends HttpMessageConverter<?>> var5) {        return var1;    }//自己写的方法,不是接口的方法,处理密文    public String getRequestBodyStr( InputStream inputStream,BufferedReader bufferedReader) throws IOException {        StringBuilder stringBuilder = new StringBuilder();            if (inputStream != null) {                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));                char[] charBuffer = new char[128];                int bytesRead = -1;                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {                    stringBuilder.append(charBuffer, 0, bytesRead);                }            } else {                stringBuilder.append("");            }        String string = stringBuilder.toString();        return string;    }}

3.响应加密

将返给前端的响应加密,保证数据的安全性

package com.linkus.common.filter;import com.alibaba.fastjson.JSON;import com.linkus.common.utils.Aes;import com.linkus.common.utils.DesUtil;import com.linkus.common.utils.http.HttpHelper;import io.swagger.models.auth.In;import lombok.experimental.Helper;import lombok.extern.slf4j.Slf4j;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.core.MethodParameter;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;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;import java.lang.reflect.Field;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;/** * 请求参数 加密操作 * * @Author: Java碎碎念 * @Date: 2019/10/24 21:31 * */@Component@ControllerAdvice(basePackages = {"com.linkus.project"})@Slf4jpublic class EncryResponseBodyAdvice implements ResponseBodyAdvice<Object> {    Logger log = LoggerFactory.getLogger(getClass());    Aes aes=new Aes();    @Override    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {        return true;    }        @Override    public Object beforeBodyWrite(Object obj, MethodParameter returnType, MediaType selectedContentType,                                  Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest serverHttpRequest,                                  ServerHttpResponse serverHttpResponse) {        String returnStr = "";        Object retObj = null;        log.info("接口请求地址{}",serverHttpRequest.getURI());        //日志过滤        //retObj=infofilter.getInfoFilter(returnType,obj);        if(HttpHelper.isEncrypted(serverHttpRequest.getHeaders())) {            try {                //添加encry header,告诉前端数据已加密                //serverHttpResponse.getHeaders().add("infoe", "e=a");                //获取请求数据                String srcData = JSON.toJSONString(obj);                //加密                returnStr = aes.encrypt(srcData).replace("\r\n", "");                //log.info("原始数据={},加密后数据={}", obj, returnStr);                return returnStr;            } catch (Exception e) {                log.error("异常!", e);            }        }        log.info("原始数据={}",JSON.toJSONString(obj));        return obj;    }}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。

人之所以能,是相信能。

使用注解+RequestBodyAdvice实现http请求内容加解密方式

相关文章:

你感兴趣的文章:

标签云: