1、因为正常HttpServletRequest只能获取一次body参数,无法进行修改,所以需要通过Wrapper(包装)自定义一个request
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
/**
* getInputStream获取流 然后在流中获取数据 但是这个方法只能获取一次
* 重写httpservletrequestwrapper把request保存下来
* 用过滤器把保存的request填进去 就可以多次读取了
*/
public class RequestWrapper extends HttpServletRequestWrapper {
private String body;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
throw ex;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
body = stringBuilder.toString();
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
ServletInputStream servletInputStream = new ServletInputStream() {
public boolean isFinished() {
return false;
}
public boolean isReady() {
return false;
}
public void setReadListener(ReadListener readListener) {
}
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
return servletInputStream;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
public String getBody() {
return this.body;
}
// 赋值给body字段
public void setBody(String body) {
this.body = body;
}
}
2、虽然自定义了request但是,请求不会直接使用自定义的Request,需要通过拦截器将所有的请求都转换成自定义的Request
public class WebFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
ServletRequest request = null;
if (servletRequest instanceof HttpServletRequest) {
request = new RequestWrapper((HttpServletRequest) servletRequest);
}
if (request == null) {
chain.doFilter(servletRequest, servletResponse);
} else {
chain.doFilter(request, servletResponse);
}
}
@Override
public void destroy() {
}
}
3、为WebFilter进行配置,此步骤可以省略,可以采用纯注解方式配置过滤器
@Configuration
public class FilterConfig {
@Bean
FilterRegistrationBean<WebFilter> myFilterFilterRegistrationBean() {
FilterRegistrationBean<WebFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new WebFilter());
bean.setOrder(-1);
bean.setUrlPatterns(Collections.singletonList("/*"));
return bean;
}
}
4、自定义注解,这里时为了实现加解密
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface DesDecrypt {
}
4、创建拦截器,对所有的标注了@DesDecrypt 的接口进行拦截,并使用自定义的request进行处理,将body参数取出,处理之后在注入回去
参数使用json字符串传递,使用data参数传递,接口中的参数依然正常使用自定义对象承接并且使用@Validated @RequestBody 注解,并出发参数校验
@Slf4j
@Component
public class DesDecryptInterceptor implements HandlerInterceptor {
private final ObjectMapper objectMapper;
public DesDecryptInterceptor(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
DesDecrypt desDecryptAnnotation = handlerMethod.getMethodAnnotation(DesDecrypt.class);
if (desDecryptAnnotation != null) {
RequestWrapper requestWrapper = (RequestWrapper) request;
String requestBody = requestWrapper.getBody();
if (requestBody == null || requestBody.isEmpty()) {
throw new RuntimeException("请求体为空");
}
JSONObject jsonObject = JSON.parseObject(requestBody);
if (!jsonObject.containsKey("data")) {
throw new RuntimeException("请求体中不存在data字段");
}
String data = jsonObject.getString("data");
log.info("Decrypted request data: {}", data);
requestWrapper.setBody(data);
}
}
return true;
}
private String readRequestBody(HttpServletRequest request) throws IOException {
byte[] buffer = new byte[request.getContentLength()];
int bytesRead = request.getInputStream().read(buffer);
return new String(buffer, 0, bytesRead, StandardCharsets.UTF_8);
}
}
5、性能测试
5.1 创建一个测试接口
@DesDecrypt
@PostMapping("/parameterPreProcess")
public String parameterPreProcess(@Validated @RequestBody ControlFrontParam param) {
log.info("param json: {}", JSON.toJSONString(param));
return JSON.toJSONString(param);
}
5.2 在使用DesDecrypt注解标记下进行访问(经过过滤器和拦截器)
{
"data": "{\"name\": \"qR)\",\"age\": 51,\"idCard\": \"54829220331210989X\"}"
}
访问多次,Time在21~29ms之间,多数在21~25ms
5.3 在不使用DesDecrypt注解标记下进行访问
{ "idCard": "54829220331210989X", "name": "zq", "age": 10 }
访问多次,Time在17~28ms之间,多数在18~23ms
*总体来说,经过过滤器和拦截器处理后,会影响响应时间,但是影响不大,对于带给整个项目在加解密方面的便捷性来说,是值得的;
通过这种方式实现的参数加密传输,只需要通过修改注解,即改变接口是否加密,对接口的结构、参数、以及参数校验都没有影响。