@RequestBody参数已经被读取,究竟是何原因?

Wesley13
• 阅读 671

不知道你们有没有对用户输入的东西进行过敏感校验,如果不进行校验,用户属于一些攻击脚本,那么我们的服务就挂逼啦!所以我们首先需要通过过滤器将用户的数据读出来进行安全校验,这里面涉及到一个动作,就是需要将用户的数据在过滤器中读出来,进行校验,通过之后再放行。

问题

如果我们的数据是get请求倒还好,但是如果是一些数据量比较大,我们需要通过post json的方式来说传递数据的时候,这个时候其实是通过流的方式传递的,如果在过滤器中将参数读取出来之后,然后放行,等到到Servlet的时候,@RequestBody是无法获取到数据的,因为post json使用流传递,流被读取之后就不存在了,所以我们在过滤器中读取之后,@ReqeustBody自然就读不到数据了,同时会报如下一个错误。

  • 在过滤器中读取body中的数据

    @WebFilter @Slf4j public class CheckUserFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
    
        HttpServletRequest request = (HttpServletRequest) req;
    
        // 在过滤器中读取数据
        BufferedReader reader = request.getReader();
    
        StringBuilder sb = new StringBuilder();
    
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
        reader.close();
    
        System.out.println(sb.toString());
    
        filterChain.doFilter(request, res);
    }
    

    }

  • 出现异常,就是说内容已经被读取了,你不能调用了

    { "id":"1", "username":"bingfeng"} java.lang.IllegalStateException: UT010003: Cannot call getInputStream(), getReader() already called at io.undertow.servlet.spec.HttpServletRequestImpl.getInputStream(HttpServletRequestImpl.java:666) at javax.servlet.ServletRequestWrapper.getInputStream(ServletRequestWrapper.java:152) at javax.servlet.ServletRequestWrapper.getInputStream(ServletRequestWrapper.java:152)

解决

  • HttpServletRequestWrapper

那么出现这种问题怎么办呢?能不能通过一个中间的变量将这些数据保存下来,然后我们就可以一直读取了,这样不就解决了这个问题了吗?那保存在哪里呢?这个时候 HttpServletRequestWrapper 就排上用场了。

这个其实你可以把它理解为Request的包装类,Reqeust中有的方法它都有,我们通过继承这个类,重写该类中的方法,将body中的参数保存一个byte数组中,然后放行的时候将这个包装类传递进去,不就可以一直拿到参数了?

  • 封装Request类

    public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private final byte[] body;
    
    /**
     * 所有参数的集合
     */
    private Map<String, String[]> parameterMap;
    
    
    public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        BufferedReader reader = request.getReader();
        body = readBytes(reader);
        parameterMap = request.getParameterMap();
    }
    
    
    @Override
    public BufferedReader getReader() throws IOException {
    
        ServletInputStream inputStream = getInputStream();
    
        if (null == inputStream) {
            return null;
        }
    
        return new BufferedReader(new InputStreamReader(inputStream));
    }
    
    @Override
    public Enumeration<String> getParameterNames() {
        Vector<String> vector = new Vector<>(parameterMap.keySet());
        return vector.elements();
    }
    
    @Override
    public ServletInputStream getInputStream() throws IOException {
    
        if (body == null) {
            return null;
        }
    
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream() {
    
            @Override
            public boolean isFinished() {
                return false;
            }
    
            @Override
            public boolean isReady() {
                return false;
            }
    
            @Override
            public void setReadListener(ReadListener listener) {
    
            }
    
            @Override
            public int read() throws IOException {
                return bais.read();
            }
        };
    }
    
    /**
     * 通过BufferedReader和字符编码集转换成byte数组
     *
     * @param br
     * @return
     * @throws IOException
     */
    private byte[] readBytes(BufferedReader br) throws IOException {
        String str;
        StringBuilder retStr = new StringBuilder();
        while ((str = br.readLine()) != null) {
            retStr.append(str);
        }
        if (StringUtils.isNotBlank(retStr.toString())) {
            return retStr.toString().getBytes(StandardCharsets.UTF_8);
        }
        return null;
    }
    

    }

  • 将过滤器改造

    @WebFilter @Slf4j public class CheckUserFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
    
        HttpServletRequest request = (HttpServletRequest) req;
    
        BodyReaderHttpServletRequestWrapper requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
    
        // 从Request的包装类中读取数据
        BufferedReader reader = requestWrapper.getReader();
    
        StringBuilder sb = new StringBuilder();
    
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
        reader.close();
    
        System.out.println(sb.toString());
    
        filterChain.doFilter(requestWrapper, res);
    }
    

    }

经过这样的配置之后,我们即使在过滤器中获取了参数,请求也会到达Servlet。

如果基础知识IO那块不是很扎实的话,第一眼看到这个问题确实挺懵逼的。我也是百度之后解决的,确实值得记录一下,有时候我们会对所有请求进来的参数进行保存输出什么的,这个时候如果是post json数据的话,如果不是特别明白,可能也会出现这种问题。

日拱一卒,功不唐捐

更多内容请关注

@RequestBody参数已经被读取,究竟是何原因?

点赞
收藏
评论区
推荐文章
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
jango Form表单组件
Form介绍 我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来。与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确。如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息.。Djangoform组件就实
Easter79 Easter79
3年前
Spring校验@RequestParams和@PathVariables参数
我们在写RestAPI接口时候会用到很多的@RequestParam和@PathVariable进行参数的传递,但是在校验的时候,不像使用@RequestBody那样的直接写在实体类中,我们这篇文章讲解一下如何去校验这些参数。依赖配置要使用JavaValidationAPI,我们必须添加validationap
Wesley13 Wesley13
3年前
Java中的参数验证(非Spring版)
1\.Java中的参数验证(非Spring版)1.1.前言为什么我总遇到这种非正常问题,我们知道很多时候我们的参数校验都是放在controller层的传入参数进行校验,我们常用的校验方式就是引入下列的jar包,在参数中添加@Validated,并对Bean对象的参数做不
Stella981 Stella981
3年前
App开放接口api安全性的设计与实现
前言在app开放接口api的设计中,避免不了的就是安全性问题,因为大多数接口涉及到用户的个人信息以及一些敏感的数据,所以对这些接口需要进行身份的认证,那么这就需要用户提供一些信息,比如用户名密码等,但是为了安全起见让用户暴露的明文密码次数越少越好,我们一般在web项目中,大多数采用保存的session中,然后在存一份到cookie中,来保持
Stella981 Stella981
3年前
Redis如何保证接口的幂等性?
在最近的一次业务升级中,遇到这样一个问题,我们设计了新的账户体系,需要在用户将应用升级之后将原来账户的数据手动的同步过来,就是需要用户自己去触发同步按钮进行同步,因为有些数据是用户存在自己本地的。那么在这个过程中就存在一个问题,要是因为网络的问题,用户重复点击了这个按钮怎么办?就算我们在客户端做了一些处理,在同步的过程中,不能再次点击,但是经过我最近
Stella981 Stella981
3年前
Django框架
Form介绍 我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来。与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确。如果用户输入的内容有错误就需要在页面上相应的位置显示显示对应的错误信息.。Djangoform组件
Stella981 Stella981
3年前
SpringBoot使用validator校验
在前台表单验证的时候,通常会校验一些数据的可行性,比如是否为空,长度,身份证,邮箱等等,那么这样是否是安全的呢,答案是否定的。因为也可以通过模拟前台请求等工具来直接提交到后台,比如postman这样的工具,那么遇到这样的问题怎么办呢,我们可以在后台也做相应的校验。新建项目,因为本文会使用postman模拟前端请求,所以本文需要加入web依赖,pom文件如
Easter79 Easter79
3年前
SpringBoot使用validator校验
在前台表单验证的时候,通常会校验一些数据的可行性,比如是否为空,长度,身份证,邮箱等等,那么这样是否是安全的呢,答案是否定的。因为也可以通过模拟前台请求等工具来直接提交到后台,比如postman这样的工具,那么遇到这样的问题怎么办呢,我们可以在后台也做相应的校验。新建项目,因为本文会使用postman模拟前端请求,所以本文需要加入web依赖,pom文件如
京东云开发者 京东云开发者
6个月前
SpringBoot如何优雅的进行参数校验(一)
SpringBoot如何优雅的进行参数校验一.为什么要进行参数校验在日常的开发过程中,我们常常需要对传入的参数进行校验,比如在web前后端分离项目中,参数校验有两个方面:前端进行参数校验后端进行参数校验那这两种有什么区别呢?只完成一个可不可以呢?答案是不可