SpringBoot通过过滤器获取请求参数

一、背景 

      在项目开发中,有时候需要从请求体中获取参数,并对参数进行sql注入、xss攻击校验或者对参数做一些签名验证,这些验证逻辑一般都是统一放到过滤器或拦截器里,这样就不用每个接口都去重复编写验签的逻辑。

        对于接口有可能接收不同类型的数据,对于表单数据来说,只要调用request的getParameterMap就能全部取出来。对于json数据来说,需要通过request的输入流去读取。但问题在于request的输入流只能读取一次不能重复读取,所以我们在过滤器或拦截器里读取了request的输入流之后,请求走到controller层时就会报错。而本文的目的就是介绍如何解决在这种场景下遇到HttpServletRequest的输入流只能读取一次的问题。

二、原因分析

        HttpServletRequest 对象中调用getInputStream()方法获取输入流时得到的是一个InputStream对象,而实际类型是ServletInputStream,它继承于InputStreamInputStreamread()方法内部有一个postion,标志当前流被读取到的位置,每读取一次,该标志就会移动一次,如果读到最后,read()会返回-1,表示已经读取完了。如果想要重新读取则需要调用reset()方法,position就会移动到上次调用mark的位置,mark默认是0,所以就能从头再读了。调用reset()方法的前提是已经重写了reset()方法,当然能否reset也是有条件的,它取决于markSupported()方法是否返回true。但是InputStream默认不实现reset(),并且markSupported()默认也是返回false

        通过查看ServletInputStream源码得知,可以看到该类没有重写mark()reset()以及markSupported()方法,这样就无法重复读取流,这就是我们从request对象中获取的输入流就只能读取一次的原因。

三、解决方案

方案1、 自定义实现HttpServletRequestWrapper

        HttpServletRequestWrapper是tomcat 提供的基于HTTP 的Servlet 请求包装类,继承自ServletRequestWrapper,并实现了HttpServletRequest,所以它本质上也是一个HttpServletRequest。可以看到该类的构造函数,调用了父类的构造,然后所有的执行方法,都会先调用_getHttpServletReques获取到父类的HttpServletRequest,再通过HttpServletRequest获取请求中的信息。换一种方式来说,HttpServletRequestWrapper 就是一个请求包装类,我们可以通过该类对请求进行装饰,比如对参数、编码方式等等进行重新设置。

方案2、使用ContentCachingRequestWrapper

        ContentCachingRequestWrapper是官方提供的HttpServletRequestWrapper的子类,通过名称可以看出,是对请求内容进行缓存,可以使用该类获取请求参数。

        本篇文章主要是方案1的方式,有关ContentCachingRequestWrapper的内容可以自行查阅相关资料。

四、代码

        新建 RequestWrapper.java 重写 HttpServletRequestWrapper,首先我们要定义一个容器,将输入流里面的数据存储到这个容器里,这个容器可以是数组或集合。然后我们重写getInputStream方法,每次都从这个容器里读数据,这样我们的输入流就可以读取任意次了。

package com.test.xxx.filter;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

public class RequestWrapper extends HttpServletRequestWrapper {

    private final byte[] body;

    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        try (InputStream inputStream = request.getInputStream()) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

            byte[] buffer = new byte[1024];
            int length;
            while ((length = inputStream.read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, length);
            }

            body = byteArrayOutputStream.toByteArray();
        }
    }

    @Override
    public ServletInputStream getInputStream() {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);

        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }

            @Override
            public boolean isFinished() {
                return byteArrayInputStream.available() == 0;
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener listener) {
            }
        };
    }

    @Override
    public BufferedReader getReader() {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    public byte[] getBody() {
        return this.body;
    }
}

        新建 CommonFilter.java,将自定义的HttpServletRequest实现类对象替换到请求链中(并不是所有的请求都需要替换,例如上传文件的请求,不需要缓存InputStream中的数据,这需要通过请求中的Content-type进行判断)在过滤器里将原生的HttpServletRequest对象替换成我们的RequestWrapper对象。

package com.test.xxx.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;


@Slf4j
@WebFilter
public class CommonFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        String contentType = servletRequest.getContentType();
        if (servletRequest instanceof HttpServletRequest) {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            String url = request.getRequestURI();
            String method = request.getMethod();
            if (contentType != null && contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) {
                log.info("接口 {} 文件上传 不过滤", url);
                filterChain.doFilter(servletRequest, servletResponse);
            } else if (HttpMethod.POST.matches(method)) {
                RequestWrapper requestWrapper = new RequestWrapper(request);
                String body = new String(requestWrapper.getBody(), servletRequest.getCharacterEncoding());
                log.info("接口 {} 请求方法 {} 过滤参数 {}", url, method, body);
                filterChain.doFilter(requestWrapper, servletResponse);
            } else if (HttpMethod.GET.matches(method)) {
                String queryString = request.getQueryString();
                log.info("接口 {} 请求方法 {} 过滤参数 {}", url, method, queryString);
                filterChain.doFilter(servletRequest, servletResponse);
            } else {
                filterChain.doFilter(servletRequest, servletResponse);
            }
        } else {
            filterChain.doFilter(servletRequest, servletResponse);
        }

    }
}

  • 23
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
可以通过自定义过滤器,在过滤器中修改请求参数,实现调整某个方法请求参数。具体实现步骤如下: 1.编写自定义过滤器类,继承`javax.servlet.Filter`类,实现`doFilter()`方法,在该方法中获取请求参数,修改请求参数,然后将请求传递给下一个过滤器或者目标方法。 2.在SpringBoot主类中添加自定义过滤器,通过`@Bean`注解将自定义过滤器注入到Spring容器中。 下面是一个示例代码,演示如何通过自定义过滤器修改请求参数: ```java @Component public class MyFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 获取请求参数 HttpServletRequest req = (HttpServletRequest) request; String parameter = req.getParameter("param"); // 修改请求参数 if (parameter != null && !parameter.isEmpty()) { req.setAttribute("newParam", parameter + "new"); } // 将修改后的请求传递给下一个过滤器或者目标方法 chain.doFilter(request, response); } } @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } // 注册自定义过滤器 @Bean public FilterRegistrationBean<MyFilter> myFilter() { FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new MyFilter()); registrationBean.addUrlPatterns("/api/*"); return registrationBean; } } @RestController @RequestMapping("/api") public class ApiController { @GetMapping("/test") public String test(@RequestParam("param") String param) { return "param: " + param; } } ``` 在上面的示例中,自定义过滤器`MyFilter`会拦截所有以`/api`开头的请求,并在`doFilter()`方法中修改请求参数`param`,将其值添加了一个后缀`new`,然后将修改后的请求传递给目标方法。在`ApiController`中,我们定义了一个`/test`接口,接口中的`param`参数会被自定义过滤器修改后再传递给目标方法,最终返回给客户端。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值