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

网友投稿 251 2023-01-02

使用注解+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 previewContract(@RequestBody FillContractDTO fillContractDTO) {

return contractSignService.previewContract(fillContractDTO);

}

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

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

@Slf4j

@RestControllerAdvice

public 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 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;

@Component

public 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"})

@Slf4j

public 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 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, HttpInputMessagNAQCgUe 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"})

@Slf4j

public class EncryResponseBodyAdvice implements ResponseBodyAdvice {

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;

}

}

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:图片api接口网站(如何搭建自己的图片api接口)
下一篇:壹米达快递物流查询单号(壹米快递单号查询快递查询)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~