如何在过滤器中修改http请求体和响应体
|字数总计:2.9k|阅读时长:11分钟|阅读量:
个人博客
在一些业务场景中,需要对http的请求体和响应体做加解密的操作,如果在controller中来调用加解密函数,会增加代码的耦合度,同时也会增加调试的难度。
参考spring中http请求的链路,选择过滤器来对请求和响应做加解密的调用。只需要在过滤器中对符合条件的url做拦截处理即可。
一般在过滤器中修改请求体和响应体,以往需要自行创建Wrapper包装类,从原请求Request对象中读取原请求体,修改后重新放入新的请求对象中等等操作……非常麻烦。如果可以在过滤器中只定义加解密的函数,然后调用一个API传入这些加解密函数,中间操作统统不管,这样用起来岂不是更爽!
1、启动类配置注解
新增注解@ServletComponentScan
1 2 3 4 5 6 7
| @SpringBootApplication @ServletComponentScan public class HttpdecryptApplication { public static void main(String[] args) { SpringApplication.run(HttpdecryptApplication.class, args); } }
|
2、过滤器实现
2.1、用Base64算法做加解密示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| @WebFilter(urlPatterns = {"/decrypt/*"}, filterName = "decryptFilter") @Slf4j public class DecryptFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest originalRequest = (HttpServletRequest) request; HttpServletResponse originalResponse = (HttpServletResponse) response;
String originalRequestBody = ServletUtil.readRequestBody(originalRequest); String modifyRequestBody = this.decryptBody(originalRequestBody); HttpServletRequest orginalRequest = (HttpServletRequest) request; ModifyRequestBodyWrapper requestWrapper = new ModifyRequestBodyWrapper(orginalRequest, modifyRequestBody);
ModifyResponseBodyWrapper responseWrapper = new ModifyResponseBodyWrapper(originalResponse); chain.doFilter(requestWrapper, responseWrapper); String originalResponseBody = responseWrapper.getResponseBody(); String modifyResponseBody = this.encryptBody(originalResponseBody);
originalResponse.setContentType(requestWrapper.getOrginalRequest().getContentType()); byte[] responseData = modifyResponseBody.getBytes(responseWrapper.getCharacterEncoding()); originalResponse.setContentLength(responseData.length); @Cleanup ServletOutputStream out = originalResponse.getOutputStream(); out.write(responseData); }
private String decryptBody(String originalBody) { return Base64.decodeToString(originalBody); }
private String encryptBody(String originalBody) { return Base64.encodeToString(originalBody); } }
|
使用步骤:
- 实现
Filter
接口。 - 使用
@WebFilter
注解指定拦截的url,可以配置多个url。
处理逻辑
- 从servlet中读取原请求体(密文)。
- 调用解密函数获得明文。
- 构建新的请求对象,包装修改后的请求体(明文)。
- 构建新的响应对象,调用链调用应用层获得响应。
- 从新的响应对象中获得响应体(明文)。
- 调用加密函数对响应体进行加密。
- 用原响应对象的输出流,将加密后的密文响应体输出。
函数中使用的请求包装类ModifyRequestBodyWrapper
和响应包装类ModifyResponseBodyWrapper
在文末附录中贴出,可以直接copy到项目工程中使用。
3、测试验证
1 2 3 4 5 6 7 8 9 10
| @RestController @Slf4j @RequestMapping("/decrypt") public class WebController { @PostMapping("/test") public String test(@RequestBody String requestBody) { log.info("经过解密后的数据:{}", requestBody); return "success-交易成功"; } }
|
1 2 3 4 5 6 7 8 9 10 11
| public class HttpdecryptApplicationTests { @Test public void test() { HttpResponse response = HttpRequest .post("http://127.0.0.1:10400/decrypt/test") .body("eyJlbmNyeXB0SW5mbyI6IuWKoOWvhuaVsOaNriIsInZlcnNpb24iOiIxLjAifQ==") .send(); String result = response.bodyText(); System.out.println(Base64.decodeToString(result)); } }
|
4、优化改进
以上就是以往的处理方式;对于过滤器中的处理逻辑,如果项目中做不同的加解密每次都要这样去实现,未免有些冗余。
重新分析不难发现在过滤器中的处理逻辑始终都是不变的,对于不同的加解密方式只有加解密函数是变化的。为此可以引入函数式编程的方式,对于处理逻辑进行封装,每次只需要定义不同的加解密函数然后调用封装好的API即可。
改进后的过滤器
1 2 3 4 5 6 7 8 9 10
| @WebFilter(urlPatterns = {"/decrypt/*"}, filterName = "decryptFilter") @Slf4j public class DecryptFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { Function<String, String> modifyRequestBodyFun = Base64::decodeToString; Function<String, String> modifyResponseBodyFun = Base64::encodeToString; HttpUtil.modifyHttpData(request, response, chain, modifyRequestBodyFun, modifyResponseBodyFun); } }
|
- 只需要在过滤器上配置需要拦截的url列表、定义加解密函数然后调用封装好的API即可。
- 过滤器中不会改变请求和响应的字符集,都是沿用原来的。
- 只能针对于带有请求体的请求做加解密处理。
- 另外
modifyHttpData
函数有另外的重载,支持修改Content-Type
。
HttpUtil
也在文末附录中贴出,直接copy到项目工程中使用。
对于函数式编程不熟悉的同学可以去学习下Java中如何使用 lambda
表达式和Java的几种内置的函数接口(JDK1.8版本及以上才支持);上面的lambda
表达式其实是一种简写的方式,还可以用其最一般化的方式来表示。
1 2 3 4 5 6
| Function<String, String> modifyRequestBodyFun = (originalBody) -> { return Base64.decodeToString(originalBody); }; Function<String, String> modifyResponseBodyFun = (originalBody) -> { return Base64.encodeToString(originalBody); };
|
参考链接
代码地址
附录
请求包装类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
|
@Data public class ModifyRequestBodyWrapper extends HttpServletRequestWrapper {
private HttpServletRequest orginalRequest;
private String modifyRequestBody;
private String contentType;
public ModifyRequestBodyWrapper(HttpServletRequest orginalRequest, String modifyRequestBody) { this(orginalRequest, modifyRequestBody, null); }
public ModifyRequestBodyWrapper(HttpServletRequest orginalRequest, String modifyRequestBody, String contentType) { super(orginalRequest); this.modifyRequestBody = modifyRequestBody; this.orginalRequest = orginalRequest; this.contentType = contentType; }
@Override @SneakyThrows public ServletInputStream getInputStream() { return new ServletInputStream() { private InputStream in = new ByteArrayInputStream(modifyRequestBody.getBytes(orginalRequest.getCharacterEncoding()));
@Override public int read() throws IOException { return in.read(); }
@Override public boolean isFinished() { return false; }
@Override public boolean isReady() { return false; }
@Override public void setReadListener(ReadListener readListener) {
} }; }
@Override @SneakyThrows public int getContentLength() { return modifyRequestBody.getBytes(orginalRequest.getCharacterEncoding()).length; }
@Override @SneakyThrows public long getContentLengthLong() { return modifyRequestBody.getBytes(orginalRequest.getCharacterEncoding()).length; }
@Override public String getContentType() { return StringUtils.isBlank(contentType) ? orginalRequest.getContentType() : contentType; }
@Override public Enumeration<String> getHeaders(String name) { if (null != name && name.replace("-", "").toLowerCase().equals("contenttype") && !StringUtils.isBlank(contentType)) { return new Enumeration<String>() { private boolean hasGetted = false;
@Override public boolean hasMoreElements() { return !hasGetted; }
@Override public String nextElement() { if (hasGetted) { throw new NoSuchElementException(); } else { hasGetted = true; return contentType; } } }; } return super.getHeaders(name); } }
|
响应包装类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
|
@Data public class ModifyResponseBodyWrapper extends HttpServletResponseWrapper {
private HttpServletResponse originalResponse;
private ByteArrayOutputStream baos;
private ServletOutputStream out;
private PrintWriter writer;
@SneakyThrows public ModifyResponseBodyWrapper(HttpServletResponse resp) { super(resp); this.originalResponse = resp; this.baos = new ByteArrayOutputStream(); this.out = new SubServletOutputStream(baos); this.writer = new PrintWriter(new OutputStreamWriter(baos)); }
@Override public ServletOutputStream getOutputStream() { return out; }
@Override public PrintWriter getWriter() { return writer; }
public String getResponseBody() throws IOException { return this.getResponseBody(null); }
public String getResponseBody(String charset) throws IOException {
out.flush(); writer.flush(); return new String(baos.toByteArray(), StringUtils.isBlank(charset) ? this.getCharacterEncoding() : charset); }
class SubServletOutputStream extends ServletOutputStream { private ByteArrayOutputStream baos;
public SubServletOutputStream(ByteArrayOutputStream baos) { this.baos = baos; }
@Override public void write(int b) { baos.write(b); }
@Override public void write(byte[] b) { baos.write(b, 0, b.length); }
@Override public boolean isReady() { return false; }
@Override public void setWriteListener(WriteListener writeListener) {
} } }
|
HttpUtil封装工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| @Slf4j public class HttpUtil {
public static void modifyHttpData(ServletRequest originalRequest, ServletResponse originalResponse, FilterChain chain, Function<String, String> modifyRequestBodyFun, Function<String, String> modifyResponseBodyFun) throws IOException, ServletException { modifyHttpData(originalRequest, originalResponse, chain, modifyRequestBodyFun, modifyResponseBodyFun, null); }
public static void modifyHttpData(ServletRequest request, ServletResponse response, FilterChain chain, Function<String, String> modifyRequestBodyFun, Function<String, String> modifyResponseBodyFun, String requestContentType) throws IOException, ServletException {
HttpServletRequest originalRequest = (HttpServletRequest) request; HttpServletResponse originalResponse = (HttpServletResponse) response;
String originalRequestBody = ServletUtil.readRequestBody(originalRequest); String modifyRequestBody = modifyRequestBodyFun.apply(originalRequestBody); ModifyRequestBodyWrapper requestWrapper = modifyRequestBodyAndContentType(originalRequest, modifyRequestBody, requestContentType);
ModifyResponseBodyWrapper responseWrapper = getHttpResponseWrapper(originalResponse); chain.doFilter(requestWrapper, responseWrapper); String originalResponseBody = responseWrapper.getResponseBody(); String modifyResponseBody = modifyResponseBodyFun.apply(originalResponseBody);
originalResponse.setContentType(requestWrapper.getOrginalRequest().getContentType()); byte[] responseData = modifyResponseBody.getBytes(responseWrapper.getCharacterEncoding()); originalResponse.setContentLength(responseData.length); @Cleanup ServletOutputStream out = originalResponse.getOutputStream(); out.write(responseData); }
public static ModifyRequestBodyWrapper modifyRequestBody(ServletRequest request, String modifyRequestBody) { return modifyRequestBodyAndContentType(request, modifyRequestBody, null); }
public static ModifyRequestBodyWrapper modifyRequestBodyAndContentType(ServletRequest request, String modifyRequestBody, String contentType) { log.debug("ContentType改为 -> {}", contentType); HttpServletRequest orginalRequest = (HttpServletRequest) request; return new ModifyRequestBodyWrapper(orginalRequest, modifyRequestBody, contentType); }
public static ModifyResponseBodyWrapper getHttpResponseWrapper(ServletResponse response) { HttpServletResponse originalResponse = (HttpServletResponse) response; return new ModifyResponseBodyWrapper(originalResponse); } }
|