SpringBoot参数加密解密统一处理
场景
业务需要对一些特定接口进行入参的解密及出参的加密操作。第一反应是使用Spring的AOP来完成,但是印象中有@ControllerAdvice对出参进行了统一包装。是否可以再定义一个@ControllerAdvice来对出参进行统一加密及入参进行统一解密呢?
简介
ResponseBodyAdvice:此接口在controller执行return之后,在response返回给浏览器之前执行。在这里我们可以对response做一些统一处理。
public interface ResponseBodyAdvice<T> {
/**
* Whether this component supports the given controller method return type
* and the selected {@code HttpMessageConverter} type.
* @param returnType the return type
* @param converterType the selected converter type
* @return {@code true} if {@link #beforeBodyWrite} should be invoked;
* {@code false} otherwise
*/
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
/**
* Invoked after an {@code HttpMessageConverter} is selected and just before
* its write method is invoked.
* @param body the body to be written
* @param returnType the return type of the controller method
* @param selectedContentType the content type selected through content negotiation
* @param selectedConverterType the converter type selected to write to the response
* @param request the current request
* @param response the current response
* @return the body that was passed in or a modified (possibly new) instance
*/
@Nullable
T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response);
}
RequestBodyAdvice:在请求参数body到达controller之前执行。在这里我们可以对inputMessage做统一处理。
public interface RequestBodyAdvice {
/**
* Invoked first to determine if this interceptor applies.
* @param methodParameter the method parameter
* @param targetType the target type, not necessarily the same as the method
* parameter type, e.g. for {@code HttpEntity<String>}.
* @param converterType the selected converter type
* @return whether this interceptor should be invoked or not
*/
boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType);
/**
* Invoked second before the request body is read and converted.
* @param inputMessage the request
* @param parameter the target method parameter
* @param targetType the target type, not necessarily the same as the method
* parameter type, e.g. for {@code HttpEntity<String>}.
* @param converterType the converter used to deserialize the body
* @return the input request or a new instance, never {@code null}
*/
HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException;
/**
* Invoked third (and last) after the request body is converted to an Object.
* @param body set to the converter Object before the first advice is called
* @param inputMessage the request
* @param parameter the target method parameter
* @param targetType the target type, not necessarily the same as the method
* parameter type, e.g. for {@code HttpEntity<String>}.
* @param converterType the converter used to deserialize the body
* @return the same body or a new instance
*/
Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
/**
* Invoked second (and last) if the body is empty.
* @param body usually set to {@code null} before the first advice is called
* @param inputMessage the request
* @param parameter the method parameter
* @param targetType the target type, not necessarily the same as the method
* parameter type, e.g. for {@code HttpEntity<String>}.
* @param converterType the selected converter type
* @return the value to use or {@code null} which may then raise an
* {@code HttpMessageNotReadableException} if the argument is required.
*/
@Nullable
Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
}
demo
- 首先定义一个注解用于标记接口是否需要对入参解密、出参加密。
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SecurityBody {
//是否对出参加密
boolean encrypt() default false;
//是否对入参解密
boolean decrypt() default false;
}
- 出参统一加密
@Slf4j
@ControllerAdvice
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
if (methodParameter.hasMethodAnnotation(SecurityBody.class)) {
SecurityBody securityBody = methodParameter.getMethodAnnotation(SecurityBody.class);
return securityBody.encrypt();
}
return false;
}
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
try {
//todo 加密操作
String body = JacksonHelper.objectToJson(o);
return Base64Encoder.encode(body.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
log.error("参数加密异常", e);
throw new RuntimeException("参数加密异常");
}
}
}
- 入参统一解密
@Slf4j
public class DecryptHttpInputMessage implements HttpInputMessage {
private HttpInputMessage httpInputMessage;
public DecryptHttpInputMessage(HttpInputMessage httpInputMessage) {
this.httpInputMessage = httpInputMessage;
}
@Override
public InputStream getBody() throws IOException {
try {
String body = IoUtil.read(httpInputMessage.getBody(), StandardCharsets.UTF_8);
//todo 解密操作
return new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
log.error("参数解密异常", e);
throw new RuntimeException("参数解密异常");
}
}
@Override
public HttpHeaders getHeaders() {
return httpInputMessage.getHeaders();
}
}
其他
没了_(:з」∠)_
Q.E.D.