response功能详解(使用ResponseBodyAdvice统一对响应的数据进行处理)
response功能详解(使用ResponseBodyAdvice统一对响应的数据进行处理)import java.nio.charset.StandardCharsets; import java.util.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.GsonHttpM
开发遇到一个需求,就是对响应给APP的json数据进行加密处理。知道 ResponseBodyAdvice 可以干这事儿,但是一直没用过。所以,这次专门学习了一下
ResponseBodyAdvice
public interface ResponseBodyAdvice<T> {
/**
* @param returnType 响应的数据类型
* @param converterType 最终将会使用的消息转换器
* @return 返回bool,表示是否要在响应之前执行“beforeBodyWrite” 方法
*/
boolean supports(MethodParameter returnType Class<? extends HttpMessageConverter<?>> converterType);
/**
* @param body 响应的数据,也就是响应体
* @param returnType 响应的数据类型
* @param selectedContentType 响应的ContentType
* @param selectedConverterType 最终将会使用的消息转换器
* @param request
* @param response
* @return 被修改后的响应体,可以为null,表示没有任何响应
*/
@Nullable
T beforeBodyWrite(@Nullable T body MethodParameter returnType MediaType selectedContentType
Class<? extends HttpMessageConverter<?>> selectedConverterType
ServerHttpRequest request ServerHttpResponse response);
}
蛮简单的东西,通过泛型,指定需要被“拦截”的响应体对象类型。
该接口的实现会在 Controller 方法返回数据,并且匹配到了 HttpMessageConverter 之后。HttpMessageConverter 进行序列化之前执行。可以通过覆写 beforeBodyWrite 来统一的对响应体进行修改。
注意,需要通过标识 @ControllerAdvice 注解激活。
Demo
EncodeResponseBodyAdvice
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import com.google.gson.Gson;
import io.springboot.common.Message;
import io.springboot.common.exception.ServiceException;
import io.springboot.common.utils.AESUtils;
/**
*
* 对响应体进行加密
* @author Administrator
*
*/
@ControllerAdvice
public class EncodeResponseBodyAdvice implements ResponseBodyAdvice<Message<Object>> {
static final Logger LOGGER = LoggerFactory.getLogger(EncodeResponseBodyAdvice.class);
@Autowired
Gson gson;
/**
* 128位AES密钥
*/
public static final byte[] KEY = "9f5d54580044d478".getBytes();
@Override
public boolean supports(MethodParameter returnType Class<? extends HttpMessageConverter<?>> converterType) {
/**
* 返回对象必须是 Message 并且使用了 GsonHttpMessageConverter
*/
return GsonHttpMessageConverter.class.isAssignableFrom(converterType);
}
@Override
public Message<Object> beforeBodyWrite(Message<Object> body MethodParameter returnType MediaType selectedContentType Class<? extends HttpMessageConverter<?>> selectedConverterType ServerHttpRequest request ServerHttpResponse response) {
Object data = body.getData();
if (data != null) {
/**
* 重写data,进行加密
*/
body.setData(this.encode(data));
}
return body;
}
private String encode(Object data) {
String jsonValue = gson.toJson(data);
try {
String cipher = Base64.getEncoder()
.encodeToString(AESUtils.encrypt(jsonValue.getBytes(StandardCharsets.UTF_8) KEY));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("响应体AES加密:raw={} cipher={}" jsonValue cipher);
}
return cipher;
} catch (Exception e) {
throw new ServiceException(Message.fail(Message.Code.INTERNAL_SERVER_ERROR "对数据加密异常:" e.getMessage()) e);
}
}
}
AESUtils
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
/**
*
* AES
*
*
*/
public class AESUtils {
private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding";
// 获取 cipher
private static Cipher getCipher(byte[] key int model) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec(key "AES");
Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
cipher.init(model secretKeySpec);
return cipher;
}
// AES加密
public static byte[] encrypt(byte[] data byte[] key)
throws Exception NoSuchPaddingException IllegalBlockSizeException BadPaddingException {
Cipher cipher = getCipher(key Cipher.ENCRYPT_MODE);
return cipher.doFinal(data);
}
// AES解密
public static byte[] decrypt(byte[] data byte[] key) throws Exception NoSuchPaddingException IllegalBlockSizeException BadPaddingException {
Cipher cipher = getCipher(key Cipher.DECRYPT_MODE);
return cipher.doFinal(data);
}
}
演示
Controller
@RequestMapping("/test")
@RestController
public class TestController {
@GetMapping
public Object test () {
return Message.success(Collections.singletonMap("name" "SpringBoot中文社区"));
}
}
加密后的内容
成功解密
image1148×468 13.9 KB
RequestBodyAdvice
有响应,就一定有请求。RequestBodyAdvice 方法是多了一些,但是万变不离其宗。稍微阅读,便能知其意。
public interface RequestBodyAdvice {
boolean supports(MethodParameter methodParameter Type targetType
Class<? extends HttpMessageConverter<?>> converterType);
HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage MethodParameter parameter
Type targetType Class<? extends HttpMessageConverter<?>> converterType) throws IOException;
Object afterBodyRead(Object body HttpInputMessage inputMessage MethodParameter parameter
Type targetType Class<? extends HttpMessageConverter<?>> converterType);
@Nullable
Object handleEmptyBody(@Nullable Object body HttpInputMessage inputMessage MethodParameter parameter
Type targetType Class<? extends HttpMessageConverter<?>> converterType);
}
原文:https://springboot.io/t/topic/1864