注意:以下代码为伪代码,仅提供思路
使用条件:
接口入参:所有接口必须使用POST请求,且需包含@RequestBody
注解
接口出参:无固定要求,根据实际情况修改以下代码
@ControllerAdvice
public class AppControllerAdvice implements RequestBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
//保证所有接口都走向该Advice
return true;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
try {
//这里通过注解值获取使用哪种解密方式
if (parameter.getMethod().isAnnotationPresent(ApiEncrypt.class)) {
ApiEncrypt serializedField = parameter.getMethodAnnotation(ApiEncrypt.class);
if (serializedField.withBox()) {
//使用RSA解密
return new DecryptedInputMessage(inputMessage, "RSA");
} else {
//使用AES解密
return new DecryptedInputMessage(inputMessage, "AES");
}
}
return inputMessage;
} catch (Exception e) {
e.printStackTrace();
return inputMessage;
}
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
static class DecryptedInputMessage implements HttpInputMessage {
private final HttpHeaders headers;
private final InputStream body;
public DecryptedInputMessage(HttpInputMessage inputMessage, String encryptType) throws Exception {
this.headers = inputMessage.getHeaders();
String privateKey = "你的RSA私钥";
if (Objects.equals(encryptType, "RSA")) {
this.body = IOUtils.toInputStream(new String(RSA.decrypt(RSA.hexToPrivateKey(privateKey), Hex.decode(IOUtils.toString(inputMessage.getBody(),"utf-8"))), StandardCharsets.UTF_8), "utf-8");
} else {
this.body = IOUtils.toInputStream(new String(AES.decrypt(Hex.decode(IOUtils.toString(inputMessage.getBody(), StandardCharsets.UTF_8)), "1234567890123456")), "utf-8");
}
}
@Override
public InputStream getBody() throws IOException {
return body;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}
}
出参加密处理
@ControllerAdvice
//这里的param最好就是用Object,因为加密后的不再是某个返回类,而是String
public class AppResponseBodyAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
ApiEncrypt serializedField = returnType.getMethodAnnotation(ApiEncrypt.class);
if(serializedField == null){
return body;
}
if(serializedField.withBox()){
String publicKey = "RSA公钥";
return "RSA"+ new String(Hex.encode(RSA.encrypt(RSA.hexToPublicKey(publicKey), JSON.toJSONBytes(body))));
}else{
return "AES"+new String(Hex.encode(AES.encrypt(JSON.toJSONBytes(body),"AES秘钥")));
}
}
}
如果只这样用,当你的返回类不是String的时候,接口会多出两个双引号,对前端及其他同行不友好,所以还要处理一下
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
//自己搞一个MessageConvert,让他能解析掉加密后的String
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
return new MappingJackson2HttpMessageConverter() {
@Override
protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
if (object instanceof String) {
Charset charset = StandardCharsets.UTF_8;
StreamUtils.copy((String) object, charset, outputMessage.getBody());
} else {
super.writeInternal(object, type, outputMessage);
}
}
};
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter = mappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(new LinkedList<MediaType>() {{
add(MediaType.APPLICATION_JSON_UTF8);
}});
//这里是去掉双引号的精髓,把String的处理器和自定义处理器的排序提前,保证解析的时候先走这两个
converters.add(0,new StringHttpMessageConverter());
converters.add(1,converter);
}
}
这里再提一下为啥会多出来两个双引号,是因为正常不加密的情况下,你的返回类型是Response<>这种,他是一个json方法,但是你在responseAdvice的时候,把他强转成了string,但是那个时候,你的处理器已经被spring确定为了json,而在json中,字符串就是要加双引号的,他就自动给你加上了, 这也是为什么要去重写那个writeInternal的原因,然后具体的原理什么的,如果想不起来了就看一下这个
springMVC中框架对HttpMessageConverter的选择原理
然后源码是在
org.springframework.web.selvet.mvc.method.annotation.AbstractMessageConverterMethodProcessor
的writeWithMessageConverters
方法中,可以进来了之后直接看213行左右,或者搜这个
genericConverter = converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter)converter : null;