怎么用注解+RequestBodyAdvice实现http请求内容加解密方式
这篇文章主要介绍“怎么用注解+RequestBodyAdvice实现http请求内容加解密方式”,在日常操作中,相信很多人在怎么用注解+RequestBodyAdvice实现http请求内容加解密方式问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么用注解+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 { 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(); } } 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)); } 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;@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; } } } 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;@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 class="lazy" data-srcData = JSON.toJSONString(obj); //加密 returnStr = aes.encrypt(class="lazy" data-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请求内容加解密方式”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341