一、背景
針對項目已經開發完的接口,都需要加上傳輸資料加密的功能,對接口入參進行AES解密,對接口出參進行加密。考慮到盡量改動少點,使用自定義注解結合springmvc裡的RequestBodyAdvice和ResponseBodyAdvice兩個類進行實作。
RequestBodyAdvice允許針對接口請求體被讀取之前進行修改,ResponseBodyAdvice允許接口出參在被傳回之前進行修改。
二、實作
1、建立兩個自定義注解類,用來标記哪些接口需要進行加密解密。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Encrypt {
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Documented
public @interface Decrypt {
}
注意:@Decrypt配置的作用域是方法和參數上,@Encrypt則是隻在方法上。
2、建立自定義DecryptRequestAdvice類繼承RequestBodyAdviceAdapter,進行入參解密
@ControllerAdvice
@Slf4j
public class DecryptRequestAdvice extends RequestBodyAdviceAdapter {
@Autowired
private AesUtil aesUtil;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
// 當某個接口方法或參數上加了@Decrypt注解,才進行解密操作
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 {
InputStream inputStream = inputMessage.getBody();
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
String requestBody = new String(bytes);
try {
// 結合自己業務針對擷取到的requestBody内容進行修改解密等操作
String decryptData = aesUtil.decrypt(requestBody , "www.cnblogs.com/shamo89");
// 把入參修改為解密後的内容
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decryptData.getBytes());
return new HttpInputMessage() {
@Override
public InputStream getBody() throws IOException {
return byteArrayInputStream;
}
@Override
public HttpHeaders getHeaders() {
return inputMessage.getHeaders();
}
};
} catch (Exception e) {
log.error("接口入參解密出錯:{}", Throwables.getStackTraceAsString(e));
}
return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
}
}
3、建立自定義EncryptResponseAdvice類繼承ResponseBodyAdvice,進行出參加密
這裡的R是自定義的接口傳回封裝類
@ControllerAdvice
@Slf4j
public class EncryptResponseAdvice implements ResponseBodyAdvice<R> {
@Autowired
private AesUtil aesUtil;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 隻有接口上加了@Encrypt注解才進行出參加密操作
return returnType.hasMethodAnnotation(Encrypt.class);
}
@Override
public R beforeBodyWrite(R body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
try {
if (body.getData() != null) {
log.info("接口加密前傳回的資料:{}", JSONUtil.toJsonStr(body.getData()));
String encStr = aesUtil.encrypt(JSONUtil.toJsonStr(body.getData()), "www.cnblogs.com/shamo89");
log.info("接口加密後傳回的資料:{}", encStr);
body.setData(encStr);
}
} catch (Exception e) {
log.error("接口傳回資料加密出錯:{}", Throwables.getStackTraceAsString(e));
}
return body;
}
}
4、controller接口
@PostMapping("/test")
@ApiOperation(value = "測試接口加密解密")
@Encrypt
public R<UserInfoDTO> test(@Decrypt @RequestBody @Valid QueryVO vo) {
UserInfoDTO convert = Convert.convert(UserInfoDTO.class, vo);
return new R(convert);
}
三、原理說明
上面那兩個advice類型,都要使用@ControllerAdvice注解進行修飾,它其實是一個實作特殊功能的@Component,隻是針對controller進行攔截,本質還是aop,我們平常使用的全局異常處理類@ExceptionHandler,GlobalExceptionHandler,也是配合該注解使用。