一、需求
只针对
@RequestBody、 @ResponseBody
两个注解起作用
期望在request请求进入controller前做是否解密验证,response在返回前做是否加密验证
二、设计
- 添加自定义注解
@Encrypt
加解密注解(使用范围类与方法上) - 添加一个加解密注解的判定类。
- 继承
RequestBodyAdvice
重写beforeBodyWrite
方法结合判定类与外部配置确认调用是否需要加解密 -
ResponseBodyAdvice
重写beforeBodyRead
方法结合判定类与外部配置确认调用是否需要加解密
RequestBodyAdvice 与 ResponseBodyAdvice
public interface RequestBodyAdvice {
// 判断是否拦截(可以更精细化地进行判断是否拦截)
boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType);
// 进行请求前的拦截处理
HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException;
// 进行请求后的拦截处理
Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
// 对空请求体的处理
@Nullable
Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
}
public interface ResponseBodyAdvice<T> {
// 判断是否拦截(可以更精细化地进行判断是否拦截)
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
// 进行响应前的拦截处理
@Nullable
T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response);
}
三、实现
1.自定义加解密的注解
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
/**
* @description: 参数加密设置
* @Author: wts
* @Date: 2022/11/7 11:10
* @Version 1.0
*/
public @interface Encrypt {
/**
* 入参是否解密,默认解密
*/
boolean in() default true;
/**
* 返回是否加密,默认加密
*/
boolean out() default true;
}
2.实现RequestBodyAdvice、ResponseBodyAdvice 接口
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.wts.common.annotation.Encrypt;
import com.wts.common.exception.BizException;
import com.wts.utils.Sm4Util;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
/**
* @description: post请求的加密参数进行解密,返回一个JSONObject对象
* @Author: wts
* @Date: 2022/11/7 11:10
* @Version 1.0
*/
@ControllerAdvice(basePackages = "com.shensu.controller")
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {
private Logger log = LoggerFactory.getLogger(DecryptRequestBodyAdvice.class);
private final HttpSession session;
public DecryptRequestBodyAdvice(HttpSession session) {
this.session = session;
}
/**
* 该方法用于判断当前请求,是否要执行beforeBodyRead方法
* methodParameter: 方法的参数对象
* type: 方法的参数类型
* aClass: 将会使用到的Http消息转换器类类型
* 注意:此判断方法,会在beforeBodyRead 和 afterBodyRead方法前都触发一次。
*
* @return 返回true则会执行beforeBodyRead
*/
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
/**
* 在Http消息转换器执转换,之前执行
* inputMessage: 客户端的请求数据
* methodParameter: 方法的参数对象
* type: 方法的参数类型
* aClass: 将会使用到的Http消息转换器类类型
*
* @return 返回 一个自定义的HttpInputMessage
*/
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
try {
boolean encode = false;
// 判断该方法是否含有@Encrypt注解
if (methodParameter.getMethod().isAnnotationPresent(Encrypt.class)) {
//获取注解配置的包含和去除字段
Encrypt serializedField = methodParameter.getMethodAnnotation(Encrypt.class);
//入参是否需要解密
encode = serializedField.in();
}
if (encode) {
// 解密-使用解密后的数据,构造新的读取流
return new MyHttpInputMessage(inputMessage, type);
} else {
return inputMessage;
}
} catch (Exception e) {
log.error("请求参数错误:{}", e.getMessage(), e);
throw new BizException("请求参数错误!");
}
}
/**
* 在Http消息转换器执转换,之后执行
* o: 转换后的对象
* httpInputMessage: 客户端的请求数据
* methodParameter: handler方法的参数类型
* type: handler方法的参数类型
* aClass: 使用的Http消息转换器类类型
*
* @return 返回一个新的对象
*/
@Override
public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return o;
}
/**
* 参数与afterBodyRead相同,不过这个方法处理的是,body为空的情况
*/
@Override
public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return o;
}
/**
* 解密-使用解密后的数据,构造新的读取流
*/
class MyHttpInputMessage implements HttpInputMessage {
private HttpHeaders headers;
private InputStream body;
public MyHttpInputMessage(HttpInputMessage inputMessage, Type type) throws Exception {
this.headers = inputMessage.getHeaders();
String bodyStr = StringUtils.defaultString(IOUtils.toString(inputMessage.getBody(), "UTF-8"));
try {
// SM4解密
String decodeParameters = Sm4Util.decryptEcb(bodyStr);
// Feature.OrderedField:解析时增加参数不调整顺序
JSONObject decodeParaJson = JSON.parseObject(decodeParameters, Feature.OrderedField);
if (decodeParaJson != null) {
this.body = IOUtils.toInputStream(decodeParameters, "UTF-8");
return;
}
this.body = null;
} catch (Exception e) {
log.error("加密参数【{}】解密失败:{}", bodyStr, e.getMessage(), e);
throw new BizException(e.getMessage());
}
}
@Override
public InputStream getBody() {
return body;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}
}
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.wts.common.annotation.Encrypt;
import com.wts.utils.Sm4Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* @Description 返回数据加密
* 实现ResponseBodyAdvice接口,其实是对加了@RestController(也就是@Controller+@ResponseBody)注解的处理器将要返回的值进行增强处理。其实也就是采用了AOP的思想,对返回值进行一次修改。
* @Author: wts
* @Date: 2022/11/7 11:10
* @Version 1.0
*/
@ControllerAdvice(basePackages = "com.shensu.controller")
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice {
private Logger log = LoggerFactory.getLogger(EncryptResponseBodyAdvice.class);
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
return true;
}
/**
* 原controller要返回的内容
*
* @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) {
Method method = methodParameter.getMethod();
if (method.isAnnotationPresent(Encrypt.class)) {
//获取注解配置的包含和去除字段
Encrypt serializedField = methodParameter.getMethodAnnotation(Encrypt.class);
//出参是否需要加密
if (serializedField != null && !serializedField.out()) {
return body;
}
}
try {
Map<String, String> resultMap = new HashMap<>();
String result = JSON.toJSONString(body, SerializerFeature.DisableCircularReferenceDetect);
// 判断使用哪种加密方式进行加密
HttpServletRequest req = ((ServletServerHttpRequest) serverHttpRequest).getServletRequest();
Object encryptType = req.getAttribute("encryptType");
if (encryptType != null && "aes".equals(encryptType.toString())) {
//对返回json数据进行AES加密
String returnStr = AesUtil.encrypt(result);
String sign = MD5Util.encode((returnStr + MD5Util.MD5_KEY));
resultMap.put("sign", sign);
resultMap.put("result", returnstr);
} else {
// 对返回json数据进行SM4加密
String sign = MD5Util.encode((returnStr + MD5Util.MD5_KEY));
String returnStr = Sm4Util.encryptEcb(result);
resultMap.put("sign", sign);
resultMap.put("result", returnStr);
}
return resultMap;
} catch (Exception e) {
log.error("对方法method:{}返回数据进行解密出现异常:{}", methodParameter.getMethod().getName(), e.getMessage(), e);
}
return body;
}
}
3.加解密工具类
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class Sm4Util {
private static final Logger log = LoggerFactory.getLogger(Sm4Util.class);
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* 密钥
*/
private static final String S_KEY = "6d476239336e624a6966383135346441";
/**
* 编码格式
*/
private static final String ENCODING = "UTF-8";
/**
* 算法名称
*/
public static final String ALGORITHM_NAME = "SM4";
/**
* 定义分组加密模式使用:PKCS5Padding
*/
public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
/**
* 128-32位16进制;256-64位16进制
*/
public static final int DEFAULT_KEY_SIZE = 128;
/**
* 获取加密参数解密之后的数据
*
* @param params
* @return
*/
public static Map<String, Object> getParams(String params) {
Map info = new HashMap<String, Object>();
try {
if (StringUtils.isNotBlank(params)) {
String strValue = Sm4Util.decryptEcb(params);
info = JSONObject.parseObject(strValue, Map.class);
}
} catch (Exception e) {
e.printStackTrace();
}
return info;
}
//密钥****************************************
/**
* 系统产生秘钥
*
* @param keySize
* @return
* @throws Exception
* @explain
*/
public static byte[] generateKey(int keySize) throws Exception {
KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
kg.init(keySize, new SecureRandom());
return kg.generateKey().getEncoded();
}
/**
* 自动生成密钥
*
* @return
* @explain
*/
public static byte[] generateKey() throws Exception {
return generateKey(DEFAULT_KEY_SIZE);
}
/**
* 返回String类型密钥
*
* @return
* @throws Exception
*/
public static String generateKeyString() throws Exception {
return ByteUtils.toHexString(generateKey());
}
//SM4解密=================================================
/**
* sm4解密
*
* @param cipherText 16进制的加密字符串(忽略大小写)
* @return 解密后的字符串
* @explain 解密模式:采用ECB
*/
public static String decryptEcb(String cipherText) throws Exception {
// 用于接收解密后的字符串
String decryptStr = "";
// 用于接收解密后的字符串
// hexString-->byte[]
byte[] keyData = ByteUtils.fromHexString(S_KEY);
// hexString-->byte[]
byte[] cipherData = ByteUtils.fromHexString(cipherText);
// 解密
byte[] srcData = decrypt_Ecb_Padding(keyData, cipherData);
// byte[]-->String
decryptStr = new String(srcData, ENCODING);
return decryptStr;
}
/**
* sm4解密
*
* @param key 16进制密钥
* @param cipherText 16进制的加密字符串(忽略大小写)
* @return 解密后的字符串
* @explain 解密模式:采用ECB
*/
public static String decryptEcb(String key, String cipherText) throws Exception {
// 用于接收解密后的字符串
String decryptStr = "";
byte[] keyData = ByteUtils.fromHexString(key);
byte[] cipherData = ByteUtils.fromHexString(cipherText);
byte[] srcData = decrypt_Ecb_Padding(keyData, cipherData);
decryptStr = new String(srcData, ENCODING);
return decryptStr;
}
/**
* 解密
*
* @param key
* @param cipherText
* @return
* @throws Exception
* @explain
*/
public static byte[] decrypt_Ecb_Padding(byte[] key, byte[] cipherText) throws Exception {
Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
return cipher.doFinal(cipherText);
}
/**
* 生成ECB暗号
*
* @param algorithmName 算法名称
* @param mode 模式
* @param key
* @return
* @throws Exception
* @explain ECB模式(电子密码本模式:Electronic codebook)
*/
private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
cipher.init(mode, sm4Key);
return cipher;
}
//SM4加密=================================================
/**
* sm4加密
*
* @param paramsStr 待加密字符串
* @return 返回16进制的加密字符串
* @throws Exception
* @explain 加密模式:ECB 密文长度不固定,会随着被加密字符串长度的变化而变化
*/
public static String encryptEcb(String paramsStr) throws Exception {
return encryptEcb(S_KEY, paramsStr);
}
/**
* sm4加密
*
* @param hexKey 16进制密钥(忽略大小写)
* @param paramStr 待加密字符串
* @return 返回16进制的加密字符串
* @throws Exception
* @explain 加密模式:ECB 密文长度不固定,会随着被加密字符串长度的变化而变化
*/
public static String encryptEcb(String hexKey, String paramStr) throws Exception {
String cipherText = "";
// 16进制字符串-->byte[]
byte[] keyData = ByteUtils.fromHexString(hexKey);
// String-->byte[]
byte[] srcData = paramStr.getBytes(ENCODING);
// 加密后的数组
byte[] cipherArray = encrypt_Ecb_Padding(keyData, srcData);
// byte[]-->hexString
cipherText = ByteUtils.toHexString(cipherArray);
return cipherText;
}
/**
* 加密模式之Ecb
*
* @param key
* @param data
* @return
* @throws Exception
*/
public static byte[] encrypt_Ecb_Padding(byte[] key, byte[] data) throws Exception {
Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
}
//校验****************************************
/**
* 校验加密前后的字符串是否为同一数据
* @explain
* @param hexKey 16进制密钥(忽略大小写)
* @param cipherText 16进制加密后的字符串
* @param paramStr 加密前的字符串
* @return 是否为同一数据
* @throws Exception
*/
public static boolean verifyEcb(String hexKey, String cipherText, String paramStr) throws Exception {
// 用于接收校验结果
boolean flag = false;
// hexString-->byte[]
byte[] keyData = ByteUtils.fromHexString(hexKey);
// 将16进制字符串转换成数组
byte[] cipherData = ByteUtils.fromHexString(cipherText);
// 解密
byte[] decryptData = decrypt_Ecb_Padding(keyData, cipherData);
// 将原字符串转换成byte[]
byte[] srcData = paramStr.getBytes(ENCODING);
// 判断2个数组是否一致
flag = Arrays.equals(decryptData, srcData);
return flag;
}
public static void main(String[] args) throws Exception {
// 获取参数
Map<String, Object> data = Sm4Util.getParams("3503be9157de9a12d23a028311c44221f32a014d61a4148b1ddfc04a02d425cf622e06e24388eb4ec01089a2e461fa2a");
System.out.println("获取加密参数解密之后的数据:" + data);
// 自动生成密钥
String secretKey = generateKeyString();
System.out.println("自动生成密钥:" + secretKey);
// 加密
String encryptEcb = Sm4Util.encryptEcb("{\n" +
" \"id\": 11\n" +
"}");
System.out.println("加密结果:" + encryptEcb);
// 解密
String decrypt = Sm4Util.decryptEcb("3503be9157de9a12d23a028311c44221f32a014d61a4148b1ddfc04a02d425cf622e06e24388eb4ec01089a2e461fa2a");
System.out.println("解密结果:" + decrypt);
}
}