背景
最近项目要有向外部提供服务的能力,但是考虑到数据安全问题,要对接口进行加解密;实现加解密的方案有很多,比如过滤器、拦截器、继承RequestResponseBodyMethodProcessor什么的,不过我最近正在了解@ResponseBodyAdvice @RequestBodyAdvice这俩注解,本着在实践中应用的目的,就准备使用这两个注解来实现加解密功能。
然而,配置好后,请求怎么都进不到这两个注解的类里。摸索了一天的时间,@RestController 和@ResponseBody 都加了,也确认已经扫描进容器中管理了,可就是无法生效。
原因
后来发现项目中之前有对所有的controller进行返回结果的统一包装,使用的是继承RequestResponseBodyMethodProcessor类来实现;
刚刚@ResponseBodyAdvice和@RequestBodyAdvice一直无法生效,就在RequestResponseBodyMethodProcessor这里面做了加密的动作,后来不经意间,把这个类在WebMvcConfigurer中导入的代码注掉了,惊奇的发现@ResponseBodyAdvice @RequestBodyAdvice这俩注解生效 了。
所以初步定位 @ResponseBodyAdvice @RequestBodyAdvice 和RequestResponseBodyMethodProcessor 会冲突导致不生效。
解决
RequestResponseBodyMethodProcessor 里的逻辑抽取到@ResponseBodyAdvice里,本来这个也是对返回结果进行增强的,所以放到这里也非常合理。
同时扩展了加密的逻辑。
核心代码
@ControllerAdvice
public class ResponseProcessor implements ResponseBodyAdvice<Object> {
private ObjectMapper om = new ObjectMapper();
@Autowired
EncryptProperties encryptProperties;
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return methodParameter.hasMethodAnnotation(Encrypt.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
byte[] keyBytes = encryptProperties.getKey().getBytes();
try {
if(!methodParameter.hasMethodAnnotation(NoResponseWrapperAnnotation.class)){
body = new ResponseWrapper<>(body);
}
body = AESUtils.encrypt(JSONObject.toJSONString(body),encryptProperties.getKey());
} catch (Exception e) {
e.printStackTrace();
}
return body;
}
}
```
```java
@ControllerAdvice
public class RequestProcessor extends RequestBodyAdviceAdapter {
@Autowired
private EncryptProperties encryptProperties;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return methodParameter.hasMethodAnnotation(Decrypt.class) || methodParameter.hasParameterAnnotation(Decrypt.class);
}
@Override
public HttpInputMessage beforeBodyRead(final HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
byte[] body = new byte[inputMessage.getBody().available()];
inputMessage.getBody().read(body);
try {
String decrypt = AESUtils.decrypt(new String(body), encryptProperties.getKey());
final ByteArrayInputStream bais = new ByteArrayInputStream(decrypt.getBytes());
return new HttpInputMessage() {
@Override
public InputStream getBody() throws IOException {
return bais;
}
@Override
public HttpHeaders getHeaders() {
return inputMessage.getHeaders();
}
};
} catch (Exception e) {
e.printStackTrace();
}
return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
}
}
```
```java
public class AESUtils {
private static final String KEY_ALGORITHM = "AES";
private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";//默认的加密算法
public static String getKey(int len){
if(len % 16 != 0){
System.out.println("长度要为16的整数倍");
return null;
}
char[] chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
char[] uuid = new char[len];
if (len > 0) {
for (int i = 0; i < len; i++) {
int x = (int) (Math.random() * (len - 0 + 1) + 0);
uuid[i] = chars[x % chars.length];
}
}
return new String(uuid);
}
public static String byteToHexString(byte[] bytes){
StringBuffer sb = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
String strHex=Integer.toHexString(bytes[i]);
if(strHex.length() > 3){
sb.append(strHex.substring(6));
} else {
if(strHex.length() < 2){
sb.append("0" + strHex);
} else {
sb.append(strHex);
}
}
}
return sb.toString();
}
/**
* AES 加密操作
*
* @param content 待加密内容
* @param key 加密密码
* @return 返回Base64转码后的加密数据
*/
public static String encrypt(String content, String key) {
try {
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);// 创建密码器
byte[] byteContent = content.getBytes("utf-8");
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));// 初始化为加密模式的密码器
byte[] result = cipher.doFinal(byteContent);// 加密
return org.apache.commons.codec.binary.Base64.encodeBase64String(result);//通过Base64转码返回
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
/**
* AES 解密操作
*
* @param content
* @param key
* @return
*/
public static String decrypt(String content, String key) {
try {
//实例化
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
//使用密钥初始化,设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key));
//执行操作
byte[] result = cipher.doFinal(org.apache.commons.codec.binary.Base64.decodeBase64(content));
return new String(result, "utf-8");
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
private static SecretKeySpec getSecretKey(final String key) throws UnsupportedEncodingException {
//返回生成指定算法密钥生成器的 KeyGenerator 对象
// KeyGenerator kg = null;
// kg = KeyGenerator.getInstance(KEY_ALGORITHM);
//
// //AES 要求密钥长度为 128
// kg.init(128, new SecureRandom(key.getBytes()));
//
// //生成一个密钥
// SecretKey secretKey = kg.generateKey();
return new SecretKeySpec(Arrays.copyOf(key.getBytes("utf-8"), 16), KEY_ALGORITHM);// 转换为AES专用密钥
}
}
```