SpringBoot 过滤器更改 Request body ,并实现数据解密

客户端、服务端网络通信,为了安全,会对报文数据进行加解密操作。

在SpringBoot项目中,最好使用参考AOP思想,加解密与Controller业务逻辑解耦,互不影响。

以解密为例:需要在request请求到达Controller之前进行拦截,获取请求body中的密文并对其进行解密,然后把解密后的明文重新设置到request的body上。

拦截器、过滤器、Controller之间的关系

SpringBoot 过滤器更改 Request body ,并实现数据解密

如图所示,在Request对象进入Controller之前,可使用Filter过滤器和Interceptor拦截器进行拦截。

过滤器、拦截器主要差异:

1、过滤器来自于 Servlet;而拦截器来自于 Spring 框架; 2、触发时机不同:请求的执行顺序是:请求进入容器 > 进入过滤器 > 进入 Servlet > 进入拦截器 > 执行控制器(Controller) 3、过滤器是基于方法回调实现的;拦截器是基于动态代理(底层是反射)实现的; 4、过滤器是 Servlet 规范中定义的,所以过滤器要依赖 Servlet 容器,它只能用在 Web 项目中;拦截器是 Spring 中的一个组件,因此拦截器既可以用在 Web 项目中,同时还可以用在 Application 或 Swing 程序中; 5、过滤器通常是用来实现通用功能过滤的,比如:敏感词过滤、字符集编码设置、响应数据压缩等功能;拦截器更接近业务系统,所以拦截器主要用来实现项目中的业务判断的,比如:登录判断、权限判断、日志记录等业务; 

对于我们当前应用场景来说,区别就是过滤器更适用于修改request body。

具体实现分析

修改请求,会有两个问题:
1、请求体的输入流被读取,它就不能再被其他组件读取,因为输入流只能被标记、重置,并且在读取后会被消耗。
2、HttpServletRequest对象的body数据只能get,不能set,不能再次赋值。而咱们的需求是需要给HttpServletRequest body解密并重新赋值。
基于以上两个问题,咱们需要定义一个HttpServletRequest实现类,增加赋值方法,来满足我们的需求。
CustomHttpServletRequestWrapper.java

import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*;  /**  * 自定义HttpServletRequestWrapper  * qxc  * 20240622  */ public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {      private String body;      public CustomHttpServletRequestWrapper(HttpServletRequest request) {         super(request);         StringBuilder stringBuilder = new StringBuilder();         BufferedReader bufferedReader = null;         InputStream inputStream = null;         try {             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) {          } finally {             if (inputStream != null) {                 try {                     inputStream.close();                 }                 catch (IOException e) {                     e.printStackTrace();                 }             }             if (bufferedReader != null) {                 try {                     bufferedReader.close();                 }                 catch (IOException e) {                     e.printStackTrace();                 }             }         }         body = stringBuilder.toString();     }      @Override     public ServletInputStream getInputStream() throws IOException {         final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());         ServletInputStream servletInputStream = new ServletInputStream() {             @Override             public boolean isFinished() {                 return false;             }             @Override             public boolean isReady() {                 return false;             }             @Override             public void setReadListener(ReadListener readListener) {             }             @Override             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;     }      public void setBody(String body) {         this.body = body;     } } 

接下来,继续写Filter类,用于拦截请求,解析body, 解密报文,替换请求body数据。
RequestWrapperFilter.java

 import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j;  import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Map; import java.util.Objects;  /**  * 自定义Filter   * qxc  * 20240622  */ @Slf4j public class RequestWrapperFilter implements Filter {      @Override     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {         CustomHttpServletRequestWrapper customHttpServletRequestWrapper = null;         try {             HttpServletRequest req = (HttpServletRequest) request;             customHttpServletRequestWrapper = new CustomHttpServletRequestWrapper(req);             preHandle(customHttpServletRequestWrapper);         } catch (Exception e) {             log.warn("customHttpServletRequestWrapper Error:", e);         }          chain.doFilter((Objects.isNull(customHttpServletRequestWrapper) ? request : customHttpServletRequestWrapper), response);     }      public void preHandle(CustomHttpServletRequestWrapper request) throws Exception {         //仅当请求方法为POST时修改请求体         if (!request.getMethod().equalsIgnoreCase("POST")) {             return;         }         //读取原始请求体         StringBuilder originalBody = new StringBuilder();         String line;         try (BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()))) {             while ((line = reader.readLine()) != null) {                 originalBody.append(line);             }         }         String bodyText = originalBody.toString();         //json字符串转成map集合         Map<String, String> map = getMap(bodyText);         //获取解密参数,解密数据         if (map != null && map.containsKey("time") && map.containsKey("data")) {             String time = map.get("time");             String key = "基于时间戳等参数生成密钥、此处请换成自己的密钥";             String data = map.get("data");             //解密数据             String decryptedData = Cipher.decrypt(key, data);             //为请求对象重新设置body             request.setBody(decryptedData);         }     }      private Map<String, String> getMap(String text) {         ObjectMapper objectMapper = new ObjectMapper();         try {             // 将JSON字符串转换为Map             Map<String, String> map = objectMapper.readValue(text, Map.class);             return map;         } catch (IOException e) {             e.printStackTrace();             return null;         }     } } 

AES加解密算法封装
Cipher.java

import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Base64;  import javax.crypto.spec.SecretKeySpec;  /**  * 自定义AES加解密算法类   * qxc  * 20240622  */ public class Cipher {     public static String encrypt(String key, String text){         try {             SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");             javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES");             cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, secretKeySpec);             byte[] encryptedBytes = cipher.doFinal(text.getBytes("UTF-8"));             String cipherText = base64Encode(encryptedBytes);             return cipherText;         }catch (Exception ex){             ex.printStackTrace();             return "";         }     }      public static String decrypt(String key, String cipherText) {         try {             SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");             javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES");             cipher.init(javax.crypto.Cipher.DECRYPT_MODE, secretKeySpec);             byte[] decryptedBytes = cipher.doFinal(base64Decode(cipherText));             return new String(decryptedBytes, StandardCharsets.UTF_8);         }catch (Exception ex){             ex.printStackTrace();             return "";         }     }      public static String getMd5(String input) {         try {             MessageDigest md = MessageDigest.getInstance("MD5");             md.update(input.getBytes());             byte[] digest = md.digest();             BigInteger bigInt = new BigInteger(1, digest);             String md5Hex = bigInt.toString(16);             while (md5Hex.length() < 32) {                 md5Hex = "0" + md5Hex;             }             return md5Hex;         } catch (Exception e) {             e.printStackTrace();             return "";         }     }      public static String base64Encode(byte[] bytes) {         if (bytes != null && bytes.length > 0) {             return Base64.getEncoder().encodeToString(bytes);         }         return "";     }      public static byte[] base64Decode(String base64Str) {         if (base64Str != null && base64Str.length() > 0) {             return Base64.getDecoder().decode(base64Str);         }         return new byte[]{};     } } 

最后,需要在WebMvcConfigurer中配置并使用RequestWrapperFilter

import com.qxc.server.encryption.RequestWrapperFilter; import com.qxc.server.jwt.JwtInterceptor; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.core.Ordered; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;   @SpringBootApplication public class ServerApplication implements WebMvcConfigurer {      public static void main(String[] args) {         SpringApplication.run(ServerApplication.class, args);     }      @Bean     public FilterRegistrationBean servletRegistrationBean() {         RequestWrapperFilter userInfoFilter = new RequestWrapperFilter();         FilterRegistrationBean<RequestWrapperFilter> bean = new FilterRegistrationBean<>();         bean.setFilter(userInfoFilter);         bean.setName("requestFilter");         bean.addUrlPatterns("/*");         bean.setOrder(Ordered.LOWEST_PRECEDENCE);          return bean;     }      @Override     public void addInterceptors(InterceptorRegistry registry) {              } }  

这样,就实现了请求报文的解密、数据替换,而且对Controller逻辑没有影响。

发表评论

评论已关闭。

相关文章

当前内容话题