SpringBoot参数加密解密

2020-12-05   


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
  1. 首先定义一个注解用于标记接口是否需要对入参解密、出参加密。
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SecurityBody {

	//是否对出参加密
	boolean encrypt() default false;

	//是否对入参解密
	boolean decrypt() default false;

}
  1. 出参统一加密
@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("参数加密异常");
        }
    }
}
  1. 入参统一解密
@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.