Springboot如何设置过滤器及重复读取request里的body

目录
  • HttpServletRequest的输入流只能读取一次的原因
  • 重复读取body中数据的方法
    • springboot的过滤器
    • 上面的getBody的代码

需求:

request的content-type为applciation/json,进入controller之前需要把body中的参数取出来做一次处理,然后和hearder中的另一个参数做对比。

思路:

加一个过滤器,在过滤器中取出参数做处理,然后比较

注意:

body里的数据用流来读取,只能读取一次

HttpServletRequest的输入流只能读取一次的原因

我们先来看看为什么HttpServletRequest的输入流只能读一次,当我们调用getInputStream()方法获取输入流时得到的是一个InputStream对象,而实际类型是ServletInputStream,它继承于InputStream。

InputStream的read()方法内部有一个postion,标志当前流被读取到的位置,每读取一次,该标志就会移动一次,如果读到最后,read()会返回-1,表示已经读取完了。如果想要重新读取则需要调用reset()方法,position就会移动到上次调用mark的位置,mark默认是0,所以就能从头再读了。调用reset()方法的前提是已经重写了reset()方法,当然能否reset也是有条件的,它取决于markSupported()方法是否返回true。

InputStream默认不实现reset(),并且markSupported()默认也是返回false,这一点查看其源码便知:

我们再来看看ServletInputStream,可以看到该类没有重写mark(),reset()以及markSupported()方法:

综上,InputStream默认不实现reset的相关方法,而ServletInputStream也没有重写reset的相关方法,这样就无法重复读取流,这就是我们从request对象中获取的输入流就只能读取一次的原因。

重复读取body中数据的方法

这个自定义的requestWrapper继承了HttpServletRequestWrapper ,HttpServletRequestWrapper 是一个ServletRequest的包装类同时也是ServletRequest的实现类。

在这个自定义的requestWrapper里,用一个String做缓存,在构造方法里先把request的body中的数据缓存起来,然后重写了getInputStream,返回这个缓存的body,而不是从流中读取。

这样,在需要多次读取body的地方,只需要在过滤器中把原来的request换成这个自定义的request,然后把这个自定义的带缓存功能的request传到后续的过滤器链中。

public class BodyReaderRequestWrapper extends HttpServletRequestWrapper {
    private final String body;

    /**
     *
     * @param request
     */
    public BodyReaderRequestWrapper(HttpServletRequest request) throws IOException{
        super(request);
        StringBuilder sb = new StringBuilder();
        InputStream ins = request.getInputStream();
        BufferedReader isr = null;
        try{
            if(ins != null){
                isr = new BufferedReader(new InputStreamReader(ins));
                char[] charBuffer = new char[128];
                int readCount = 0;
                while((readCount = isr.read(charBuffer)) != -1){
                    sb.append(charBuffer,0,readCount);
                }
            }else{
                sb.append("");
            }
        }catch (IOException e){
            throw e;
        }finally {
            if(isr != null) {
                isr.close();
            }
        }

        sb.toString();
        body = sb.toString();
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayIns = new ByteArrayInputStream(body.getBytes());
        ServletInputStream servletIns = 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 byteArrayIns.read();
            }
        };
        return  servletIns;
    }
}

springboot的过滤器

2个注解:

  • @WebFilter(过滤器上)
  • @ServletComponentScan (加在启动类上,支持servlet components扫描(为了webfilter))
@Order(999) // 序号越低,优先级越高
// 加上WebFilter即可成为过滤器
@WebFilter(filterName="myFilter", urlPatterns="/api/workorder/service/selfAppeal")
public class ExternalFilter implements Filter  {

    private final static Logger logger = LoggerFactory.getLogger(ExternalFilter.class);
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        logger.info("filter init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        ResponseObject object = new ResponseObject();
        HttpServletRequest req = (HttpServletRequest)servletRequest;
        HttpServletResponse res = (HttpServletResponse)servletResponse;
        // 一个request的包装类,初始化时缓存了body,重写了getInputStream返回缓存的body,实现重复读取body
        BodyReaderRequestWrapper requestWrapper  = new BodyReaderRequestWrapper(req);
        String requestURI = requestWrapper.getRequestURI();
        System.out.println("--------------------->过滤器:请求地址" + requestURI);
        String md5 = requestWrapper.getHeader("md5")  ;

        if (md5 == null || !md5.toLowerCase().equals(MD5.md5(ReqGetBody.getBody(requestWrapper)).toLowerCase())) {
            object.setStatus(501);
            object.setStatusText("数据md5校验失败");
            render(object, res);
            return;
        }
        // 这里传递下去的就是自定义的request了,所以后续的Controller才能重复读取到body里的参数
        filterChain.doFilter(requestWrapper, res);
    }

    @Override
    public void destroy() {
    }

    /**
    * @Title: render
    * @Description: 发送Response
    * @param object
    * @param response void
    * @author MasterYi
    * @date 2020年1月15日上午10:48:45
    */
    private void render(ResponseObject object, HttpServletResponse response) {
        response.setContentType("application/json;charset=UTF-8");
        try {
            response.setStatus(200);
            response.getWriter().write(JSONObject.toJSON(object).toString());
        } catch (IOException e) {
            logger.error("ExternalFilter写入response异常");
        }
    }
}

上面的getBody的代码

从body中取值的具体操作

public class ReqGetBody {
    static public String getBody(HttpServletRequest request) {
        try {
            ServletInputStream in = request.getInputStream();
            String body;
            body = StreamUtils.copyToString(in, Charset.forName("UTF-8"));

            return body;
        } catch (IOException e) {
            e.printStackTrace();
            return "";
        }
    }
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • springboot HandlerIntercepter拦截器修改request body数据的操作

    实际工作中学习技术是最快.最深刻的.当然,自身的持续学习意识是必须的 技术栈版本: spring boot 2.0.2 遇到事儿了 近来做业务需求,前端同学fe将userId和userName放到request header中了. 后端api接口要想使用userId和userName,每个接口都要从header中获取. 试想一下,如果你有十个接口,那么每个接口都要写一遍 Object.setUserId(request.getHeader("userId")) 正如下面代码段 @Res

  • Springboot拦截器如何获取@RequestBody参数

    Springboot拦截器获取@RequestBody参数 HttpContextUtils import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader;

  • SpringBoot v2.2以上重复读取Request Body内容的解决方案

    目录 SpringBoot v2.2以上重复读取Request Body内容 一.需求 二.解决方案 三.遇到问题 四.问题排查 解决方案 SpringBoot 读取Request参数的坑 后端拿参数相关 关于流 SpringBoot v2.2以上重复读取Request Body内容 一.需求 项目有两个场景会用到从Request的Body中读取内容. 打印请求日志 提供Api接口,在api方法执行前,从Request Body中读取参数进行验签,验签通过后在执行api方法 二.解决方案 2.1

  • springboot接口如何多次获取request中的body内容

    1. 概述 在使用springboot开发接口时,会将参数转化为Bean,用来进行参数的自动校验.同时也想获取request中原始body报文进行验签(防止报文传输过程中被篡改). 因为通过将bean再转化为字符串后,body里面的报文格式.字段顺序会发生改变,就会导致验签失败.因此只能通过request来获取body里面的内容. 既想接口自动实现参数校验,同时又想获取request中的原始报文,因此我们可以通过在controller中的restful方法中,写入两个参数,获取多次request

  • Springboot如何设置过滤器及重复读取request里的body

    目录 HttpServletRequest的输入流只能读取一次的原因 重复读取body中数据的方法 springboot的过滤器 上面的getBody的代码 需求: request的content-type为applciation/json,进入controller之前需要把body中的参数取出来做一次处理,然后和hearder中的另一个参数做对比. 思路: 加一个过滤器,在过滤器中取出参数做处理,然后比较 注意: body里的数据用流来读取,只能读取一次 HttpServletRequest的

  • 解决SpringBoot打成jar运行后无法读取resources里的文件问题

    开发一个word替换功能时,因替换其中的内容功能需要 word 模版,就把 word_replace_tpl.docx 模版文件放到 resources 下 在开发环境中通过下面方法能读取word_replace_tpl.docx文件,但是打成jar包在 linux下运行后无法找到文件了 File file = ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + "static/office_template/xxx.docx&q

  • 如何构建可重复读取inputStream的request

    目录 构建可重复读取inputStream的request request中inputStream多次读取 原因 解决方法(缓存读取到的数据) 代码 构建可重复读取inputStream的request 我们知道,request的inputStream只能被读取一次,多次读取将报错,那么如何才能重复读取呢?答案之一是:增加缓冲,记录已读取的内容. 代码如下所示: import lombok.extern.log4j.Log4j2; import org.springframework.mock.

  • ASP.NET Core读取Request.Body的正确方法

    前言 相信大家在使用ASP.NET Core进行开发的时候,肯定会涉及到读取Request.Body的场景,毕竟我们大部分的POST请求都是将数据存放到Http的Body当中.因为笔者日常开发所使用的主要也是ASP.NET Core所以笔者也遇到这这种场景,关于本篇文章所套路的内容,来自于在开发过程中我遇到的关于Request.Body的读取问题.在之前的使用的时候,基本上都是借助搜索引擎搜索的答案,并没有太关注这个,发现自己理解的和正确的使用之间存在很大的误区.故有感而发,便写下此文,以作记录

  • SpringCloud Gateway读取Request Body方式

    目录 Gateway读取RequestBody 分析ReadBodyPredicateFactory 配置ReadBodyPredicateFactory 编写自定义GatewayFilterFactory 完整的yml配置 Gateway自定义filter获取body的数据为空 首先创建一个全局过滤器把body中的数据缓存起来 在自定义的过滤器中尝试获取body中的数据 解析body的工具类 Gateway读取Request Body 我们使用SpringCloud Gateway做微服务网关

  • Gateway网关自定义拦截器的不可重复读取数据问题

    目录 Gateway网关自定义拦截器的不可重复读取数据 统一网关Gateway 一.为什么需要网关 二.搭建网关服务 三.路由断言工厂 四.路由过滤器 五.跨域问题处理 Gateway网关自定义拦截器的不可重复读取数据 最近在开发gateway网关时,通过自定义拦截器对某些接口的数据进行处理,发现,无法读取到数据.经过查询,发现在Spring5的webflux编程或者普通web编程中,只能从request中获取body一次,后面无法再获取. 参考网上的方法先通过全局过滤器把body先缓存起来.

  • spring boot设置过滤器、监听器及拦截器的方法

    前言 其实这篇文章算不上是springboot的东西,我们在spring普通项目中也是可以直接使用的 设置过滤器: 以前在普通项目中我们要在web.xml中进行filter的配置,但是只从servlet 3.0后,我们就可以在直接在项目中进行filter的设置,因为她提供了一个注解@WebFilter(在javax.servlet.annotation包下),使用这个注解我们就可以进行filter的设置了,同时也解决了我们使用springboot项目没有web.xml的尴尬,使用方法如下所示 @

  • 解决springboot中配置过滤器以及可能出现的问题

    在springboot添加过滤器有两种方式: 1.通过创建FilterRegistrationBean的方式(建议使用此种方式,统一管理,且通过注解的方式若不是本地调试,如果在filter中需要增加cookie可能会存在写不进前端情况) 2.通过注解@WebFilter的方式 通过创建FilterRegistrationBean的方式创建多个filter以及设置执行顺序: 1.创建两个实现Filter接口的类TestFilter1 .TestFilter2 package com.aoxun.c

  • SpringBoot+Redis+Lua防止IP重复防刷攻击的方法

    黑客或者一些恶意的用户为了攻击你的网站或者APP.通过肉机并发或者死循环请求你的接口.从而导致系统出现宕机. 针对新增数据的接口,会出现大量的重复数据,甚至垃圾数据会将你的数据库和CPU或者内存磁盘耗尽,直到数据库撑爆为止. 针对查询的接口.黑客一般是重点攻击慢查询,比如一个SQL是2S.只要黑客一致攻击,就必然造成系统被拖垮,数据库查询全都被阻塞,连接一直得不到释放造成数据库无法访问. 具体要实现和达到的效果是: 需求:在10秒内,同一IP 127.0.0.1 地址只允许访问30次. 最终达到

随机推荐