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


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

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

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

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

    如图所示,在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逻辑没有影响。

  • 相关阅读:
    java-net-php-python-ssm大学生旅游平台计算机毕业设计程序
    PEI是聚醚酰亚胺(Polyetherimide)在粘接使用时使用UV胶水的优势有哪些?要注意哪些事项?
    Python股票量化投资课学习—单均线双均线策略
    ClickHouse 学习之从高级到监控以及备份(二)
    Linux 安装jenkins
    怎样判断磁场力方向
    关于使用RT-Thread系统读取stm32的adc无法连续转换的问题解决
    Linux之make/maakefile
    STM32 Cube配置RS485 Modbus
    Java学习笔记38——网络编程02
  • 原文地址:https://www.cnblogs.com/qixingchao/p/18262499