欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SpringBoot 之 SpringMVC拦截器从Request中获取参数并解决request的请求流只能读取一次的问题

程序员文章站 2024-02-02 16:02:22
...

为什么使用RequestBody只能读取一遍请求数据流?

         那是因为流对应的是数据,数据放在内存中,有的是部分放在内存中。read 一次标记一次当前位置(mark position),第二次read就从标记位置继续读(从内存中copy)数据。 所以这就是为什么读了一次第二次是空了。 怎么让它不为空呢?只要inputstream 中的pos 变成0就可以重写读取当前内存中的数据。javaAPI中有一个方法public void reset() 这个方法就是可以重置pos为起始位置,但是不是所有的IO读取流都可以调用该方法!ServletInputStream是不能调用reset方法,这就导致了只能调用一次getInputStream()。

解决办法:重写HttpServletRequestWrapper方法

        这种方法就是通过重写HttpServletRequestWrapper把request的保存下来,然后通过过滤器保存下来的request在填充进去,这样就可以多次读取request了

 

功能代码:

package com.digipower.erms.request.wrapper;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.Charset;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringEscapeUtils;

public class SQLInjectionHttpServletRequestWrapper extends HttpServletRequestWrapper {

	private final byte[] bytes;

	public SQLInjectionHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
		super(request);

		// 读取输入流里的请求参数,并保存到bytes里
		bytes = IOUtils.toByteArray(request.getInputStream());
	}

	public String getRequestBodyParame() {
		return new String(bytes, Charset.forName("utf8"));
	}

	/**
	 * 
	 * <p>
	 * Title: getInputStream
	 * </p>
	 * <p>
	 * Description:处理POST请求参数 RequestBody is missing 问题
	 * </p>
	 * 
	 * @return
	 * @throws IOException
	 * @see javax.servlet.ServletRequestWrapper#getInputStream()
	 */
	@Override
	public ServletInputStream getInputStream() throws IOException {
		String body = new String(this.bytes);
		return new BufferedServletInputStream(cleanXSS(body).getBytes());
	}

	class BufferedServletInputStream extends ServletInputStream {
		private ByteArrayInputStream inputStream;

		public BufferedServletInputStream(byte[] buffer) {
			// 此处即赋能,可以详细查看ByteArrayInputStream的该构造函数;
			this.inputStream = new ByteArrayInputStream(buffer);
		}

		@Override
		public int available() throws IOException {
			return inputStream.available();
		}

		@Override
		public int read() throws IOException {
			return inputStream.read();
		}

		@Override
		public int read(byte[] b, int off, int len) throws IOException {
			return inputStream.read(b, off, len);
		}

		@Override
		public boolean isFinished() {
			// TODO Auto-generated method stub
			return false;
		}

		@Override
		public boolean isReady() {
			// TODO Auto-generated method stub
			return false;
		}

		@Override
		public void setReadListener(ReadListener listener) {
			// TODO Auto-generated method stub

		}
	}

	/**
	 * 
	 * <p>
	 * Title: getParameterValues
	 * </p>
	 * <p>
	 * Description: 解决html 和javascript 脚本注入问题
	 * </p>
	 * 
	 * @param name
	 * @return
	 * @see javax.servlet.ServletRequestWrapper#getParameterValues(java.lang.String)
	 */
	@Override
	public String[] getParameterValues(String parameter) {
		// TODO Auto-generated method stub
		String[] values = super.getParameterValues(parameter);
		if (values == null) {
			return null;
		}
		int count = values.length;
		String[] encodedValues = new String[count];
		for (int i = 0; i < count; i++) {
			encodedValues[i] = cleanXSS(values[i]);
		}
		return encodedValues;
	}

	@Override
	public String getParameter(String parameter) {
		String value = super.getParameter(parameter);
		return value;
	}

	@Override
	public String getHeader(String name) {
		String value = super.getHeader(name);
		return value;
	}

}

2、过滤器SQLInjectionFilter

package com.digipower.erms.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

import com.digipower.erms.request.wrapper.SQLInjectionHttpServletRequestWrapper;
/**
 * 
 * @ClassName:  SQLInjectionFilter   
 * @Description: ServletRequest 读取RequestBody is miss的问题  
 * @date:   2018年11月7日 上午9:03:25   
 *     
 */
@WebFilter(filterName="SQLInjectionFilter",urlPatterns="/*")
public class SQLInjectionFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // TODO Auto-generated method stub
         if(request instanceof HttpServletRequest) {
             request = new SQLInjectionHttpServletRequestWrapper((HttpServletRequest) request);
         }
         chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

}

springboot 配置Filter

package com.digipower;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.support.SpringBootServletInitializer;
 
@SpringBootApplication
@ServletComponentScan("com.digipowert.erms.filter")
public class Application extends SpringBootServletInitializer {
 
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		 SpringApplication.run(Application.class, args);
	}
	
	@Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        // 注意这里要指向原先用main方法执行的Application启动类
        return builder.sources(Application.class);
    }
}

现在我们已经完成HttpServletRequest  转换为自定义SQLInjectionHttpServletRequestWrapper  包装对象。

下面说一下其应用场景:SQL 攻击注入、SpringAOP 参数日志记录。

SQL 攻击注入:SpringBoot 之 SpringMVC 实现SQL注入过滤 完整版

SpringAOP 参数日志记录:SpringBoot 之SpringAOP 请求日志记录功能(支持POST和GET)