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);
}
}
先使用工具类,构建一个加密之后的请求参数,如下
模拟请求:
结果如下:
上一篇: Android 调用百度人脸识别
下一篇: springmvc中Json的使用