在实际项目中,我们常常需要在Controller请求前后进行一些操作,比如:参数解密/返回结果加密,打印请求参数和返回结果的日志等。
方式一:统一包装返回值- 通常返回结果包含code、message、data,结构如下
- 缺点:每个Controller都要修改
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class ResponseResult方式二:使用RequestBodyAdvice和ResponseBodyAdvice对请求前后进行处理{ private int code; private String message; private T data; public ResponseResult(T data) { this.data = data; this.code = 0; this.message = "success"; } }
基于ControllerAdvice和HttpMessageConverter实现
注解1:标记不加密处理ExcludeHttpBodyDecrypt
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExcludeHttpBodyDecrypt {
}
注解1:标记加密处理ExcludeHttpBodyDecrypt
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HttpBodyDecrypt {
}
测试Controller接口
@RestController
@RequestMapping("/UniversityManagement")
public class BsBuUniversityInfoController {
@HttpBodyDecrypt
@PostMapping(value = "/BsBuUniversityInfo/testNoLogin", name = "测试接口")
public Object test(@RequestBody BsBuUniversityInfoModel model) {
BsBuUniversityInfo buUniversityInfo = new BsBuUniversityInfo();
buUniversityInfo.setUniversityCode("00000");
return buUniversityInfo;
}
}
通过RequestBodyAdvice,拦截Controller对request解密,签名验证处理
import cn.gewut.business.common.Annotation.ExcludeHttpBodyDecrypt;
import cn.gewut.business.common.Annotation.HttpBodyDecrypt;
import cn.gewut.business.common.Util.AESUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
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;
@ControllerAdvice
public class RequestAdvisor implements RequestBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class extends HttpMessageConverter>> converterType) {
System.out.println("supports");
//排除解密注解--包含@ExcludeHttpBodyDecrypt 返回false不执行
boolean methodHasExcludeHttpBodyDecrypt = methodParameter.hasMethodAnnotation(ExcludeHttpBodyDecrypt.class);
if (methodHasExcludeHttpBodyDecrypt) {
return false;
}
//解密注解--包含@HttpBodyDecrypt 返回true 需要执行
boolean methodHasHttpBodyDecrypt = methodParameter.hasMethodAnnotation(HttpBodyDecrypt.class);
if (methodHasHttpBodyDecrypt) {
return true;
}
//排除解密注解--声明类是否包含@ExcludeHttpBodyDecrypt 返回false不执行
boolean classHasExcludeHttpBodyDecrypt = methodParameter.getDeclaringClass().getAnnotation(ExcludeHttpBodyDecrypt.class) != null;
if (classHasExcludeHttpBodyDecrypt) {
return false;
}
boolean classHasHttpBodyDecrypt = methodParameter.getDeclaringClass().getAnnotation(HttpBodyDecrypt.class) != null;
if (classHasHttpBodyDecrypt) {
return true;
}
return false;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class extends HttpMessageConverter>> converterType) throws IOException {
System.out.println("beforeBodyRead");
if (inputMessage.getBody().available() <= 0) {
return inputMessage;
}
byte[] requestDataByte = new byte[inputMessage.getBody().available()];
inputMessage.getBody().read(requestDataByte);
byte[] requestDataByteNew = null;
try {
// 解密
requestDataByteNew = AESUtils.decryptHandler(requestDataByte);
} catch (Exception e) {
e.printStackTrace();
}
// 使用解密后的数据,构造新的读取流
InputStream rawInputStream = new ByteArrayInputStream(requestDataByteNew);
return new HttpInputMessage() {
@Override
public HttpHeaders getHeaders() {
return inputMessage.getHeaders();
}
@Override
public InputStream getBody() throws IOException {
return rawInputStream;
}
};
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class extends HttpMessageConverter>> converterType) {
System.out.println("afterBodyRead");
return body;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class extends HttpMessageConverter>> converterType) {
System.out.println("handleEmptyBody");
return null;
}
}
通过ResponseBodyAdvice,拦截Controller对Response封装,统一返回对象
import cn.gewut.business.common.Util.ResponseResult; 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.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; @ControllerAdvice public class ResponseAdvisor implements ResponseBodyAdvice用到的 AES加密,签名工具类
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class AESUtils {
public static final String AESKey = "key1234567890key";
public static final String signKey = "123456";
// 加密
public static String Encrypt(String sSrc, String sKey) throws Exception {
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes("utf-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");//"算法/模式/补码方式"
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));
return Base64.getEncoder().encodeToString(encrypted);//此处使用BASE64做转码功能,同时能起到2次加密的作用。
}
// 解密
public static String Decrypt(String sSrc, String sKey) throws Exception {
try {
// 判断Key是否正确
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes("utf-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] encrypted1 = Base64.getDecoder().decode(sSrc);//先用base64解密
try {
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original, "utf-8");
return originalString;
} catch (Exception e) {
System.out.println(e.toString());
return null;
}
} catch (Exception ex) {
System.out.println(ex.toString());
return null;
}
}
public static byte[] decryptHandler(byte[] requestBytes) throws Exception {
if (requestBytes.length <= 0) {
return new byte[0];
}
String requestData = new String(requestBytes, StandardCharsets.UTF_8);
JSONObject jsonobj = JSON.parseObject(requestData);
System.out.println("收到的加密数据:" + jsonobj);
String encrypt = jsonobj.get("encrypt") == null ? "" : jsonobj.get("encrypt").toString();
String decrypt = "";
if (encrypt.length() > 0) {
//解密参数
decrypt = AESUtils.Decrypt(encrypt, AESKey);
if (decrypt == null) {
//解密失败,返回异常
throw new IllegalArgumentException("解密失败");
}
}
System.out.println("解密后的数据:" + decrypt);
//获取并验证签名。
String sign = jsonobj.get("sign") == null ? "" : jsonobj.get("sign").toString();
String md5Data = Md5Utils.md5Digest(decrypt + signKey);
if (!sign.equals(md5Data)) {
//验签失败
throw new IllegalArgumentException("验证签名失败");
}
//验证通过,返回解密后的参数
return decrypt.getBytes(StandardCharsets.UTF_8);
}
public byte[] encryptHandler(byte[] requestBytes) throws Exception {
if (requestBytes.length <= 0) {
return new byte[0];
}
String requestData = new String(requestBytes, StandardCharsets.UTF_8);
String encrypt = "";
if (requestData.length() > 0) {
//加密密文
encrypt = AESUtils.Encrypt(requestData, AESKey);
if (encrypt == null) {
//加密失败,返回异常
throw new IllegalArgumentException("加密失败");
}
}
JSONObject jsonObj = new JSONObject();
jsonObj.put("encrypt", encrypt);//加密数据
jsonObj.put("sign", Md5Utils.md5Digest(requestData + signKey));//签名
System.out.println("加密签名后数据:" + jsonObj);
//验证通过,返回解密后的参数
return jsonObj.toJSONString().getBytes(StandardCharsets.UTF_8);
}
//测试AES加密
public static void main(String[] args) throws Exception {
String cKey = "key1234567890key";
// 需要加密的字串
String cSrc = "www.yin.com";
System.out.println(cSrc);
// 加密
String enString = Encrypt(cSrc, cKey);
System.out.println("加密后的字串是:" + enString);
// 解密
String DeString = Decrypt(enString, cKey);
System.out.println("解密后的字串是:" + DeString);
System.out.println("*******************************************");
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", "小米");
jsonObject.put("universityCode", "00000");
System.out.println("发送的接口数据:" + jsonObject);
AESUtils aesUtils = new AESUtils();
//加密
byte[] bytes = aesUtils.encryptHandler(jsonObject.toJSONString().getBytes());
//解密
byte[] bytes1 = aesUtils.decryptHandler(bytes);
}
}
原理
RequestResponseBodyAdviceChain
1. 启动执行RequestResponseBodyAdviceChain()初始化把RequestBodyAdvice,ResponseBodyAdvice实现类都加到RequestResponseBodyAdviceChain;
2. 每次请求执行beforeBodyRead(),源码可以看出循环所有实现类,然后先执行advice.supports(parameter, targetType, converterType) 为true后,执行request = advice.beforeBodyRead(request, parameter, targetType, converterType);
3. 再执行afterBodyRead()源码可以看出循环所有实现类,然后先执行advice.supports(parameter, targetType, converterType) 为true后,执行body = advice.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice



