笔者今天在使用过滤器的过程中遇到几个疑问,在这里记录一下。
一、过滤链的执行顺序
网上一些博客里面对过滤链执行顺序给出的方法是使用@Order(1)注解,通过这个注解中的参数数值大小可以确定过滤链中各个过滤器的执行顺序,但事实上是不行的,使用这个注解后,过滤器的执行顺序还是按照过滤器名称的首字母排序的。想要进行排序的话,需要使用FilterRegistrationBean中的setOrder(1)方法来进行排序调整,下面是伪代码:
@Bean
public FilterRegistrationBean<XssFilter> XssFilter() {
FilterRegistrationBean<XssFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new XssFilter());
//配置过滤规则
filterRegistrationBean.addUrlPatterns("/*");
//设置init参数,设置的参数在 Filter 的init 方法里的 FilterConfig 对象里可以获取到,即 filterConfig.getInitParameter("name")
filterRegistrationBean.addInitParameter("paramName","paramValue");
//设置过滤器名称
filterRegistrationBean.setName("XssFilter");
//执行次序
filterRegistrationBean.setOrder(1);
return filterRegistrationBean;
}
@Bean
public FilterRegistrationBean<ParamsFilter> ParamsFilter() {
FilterRegistrationBean<ParamsFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new ParamsFilter());
//配置过滤规则
filterRegistrationBean.addUrlPatterns("/*");
//设置init参数,设置的参数在 Filter 的init 方法里的 FilterConfig 对象里可以获取到,即 filterConfig.getInitParameter("name")
filterRegistrationBean.addInitParameter("paramName","paramValue");
//设置过滤器名称
filterRegistrationBean.setName("ParamsFilter");
//执行次序
filterRegistrationBean.setOrder(2);
return filterRegistrationBean;
}
将上面这段代码放到启动类中,spring容器就能识别你自定义的过滤器了,上面的代码规定的过滤链的执行顺序:XssFilter先执行,ParamsFilter后执行。结果如下:
注意:这个只是请求的执行顺序,不是过滤器的启动顺序,过滤器启动的顺序还是按照过滤器名称的首字母排序来启动的。
二、过滤器中拿到的参数问题
笔者先用浏览器发送了一串带有特殊字符的参数给服务器,如下:
但是过滤器那边拿到的数据却是:
笔者在这期间没有对它做任何处理,但是特殊字符就是被去除掉了。后来查资料说是,tomcat本身会去除一部分特殊字符,但也不知道对不对,笔者暂时先这样理解,后续有新的发现会继续更新的。
三、过滤器中添加请求参数
其实通过Xss防御过滤器就可以知道:过滤器是可以对请求参数进行修改的,但是它只表明了原有参数可以修改,那么它是否可以添加本来没有的参数呢?答案是肯定的,它是可以添加自定义参数的,下面有笔者实现的代码:
package com.muyichen.demo.security;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Enumeration;
import java.util.Map;
import java.util.Vector;
/**
* 参数设置类(当前类定义的参数类型只能是String[])
* @author muyichen
* @version 1.0
*/
public class ParameterRequestWrapper extends HttpServletRequestWrapper {
private Map<String, String[]> params;
/**
* 将自定义参数通过构造器传入当前类
* @param request
* @param newParams
*/
public ParameterRequestWrapper(HttpServletRequest request, Map<String, String[]> newParams) {
super(request);
this.params = newParams;
//重新封装参数
renewParameterMap(request);
}
/**
* 重写参数获取方法(这里实现的是获取value字符串数组中第一个数值)
* @param name
* @return
*/
@Override
public String getParameter(String name) {
String result = "";
Object v = params.get(name);
if (v == null) {
result = null;
} else {
String[] strArr = (String[]) v;
if (strArr.length > 0) {
result = strArr[0];
} else {
result = null;
}
}
return result;
}
/**
* 重写获取参数集合的方法
* @return
*/
@Override
public Map<String, String[]> getParameterMap() {
return params;
}
/**
* 重写获取参数名称的方法
* @return
*/
@Override
public Enumeration<String> getParameterNames() {
return new Vector<>(params.keySet()).elements();
}
/**
* 重写获取参数值的方法
* @param name
* @return
*/
@Override
public String[] getParameterValues(String name) {
String[] result = null;
Object v = params.get(name);
if (v != null) {
result = (String[]) v;
}
return result;
}
/**
* 重新封装参数,将新的参数加入
* @param req
*/
private void renewParameterMap(HttpServletRequest req) {
//获取参数字符串
String queryString = req.getQueryString();
//判断参数字符串是否存在
if (queryString != null && queryString.trim().length() > 0) {
//将参数字符串拆分成各个参数数组
String[] params = queryString.split("&");
//对参数数组进行循环
for (int i = 0; i < params.length; i++) {
//获取第i个参数等号的下标位置
int splitIndex = params[i].indexOf("=");
//如果等号不存在则跳过当前循环
if (splitIndex == -1) {
continue;
}
//获取第i个参数的参数名
String key = params[i].substring(0, splitIndex);
//判断现有的参数集合中是否存在当前参数
if (!this.params.containsKey(key)) {
//判断第i个参数等号的下标位置是否小于第i个参数的长度
if (splitIndex < params[i].length()) {
//获取第i个参数的参数值
String value = params[i].substring(splitIndex + 1);
//将第i个参数放入现有参数集合中
this.params.put(key, new String[] { value });
}
}
}
}
}
}
需要将上面这个Wrapper加入过滤器中才能生效:
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("执行ParamsFilter过滤操作");
Map<String, String[]> newParams = new HashMap<>();
newParams.put("name", new String[]{"wangwu##$#$"});
filterChain.doFilter(new ParameterRequestWrapper((HttpServletRequest)servletRequest, newParams), servletResponse);
}
需要注意的是:
- 由于ServletRequestWrapper中的getParameter方法的局限,这里的value只能存储字符串数组,想要设置一个对象的话,也必须转成json字符串放入字符串数组中,然后再通过内部方法转换类型才能得到想要的对象;
- 还有就是这个参数修改和增加的方法,只适用于get请求,也就是说在url中明文传输的参数才能使用这个方法修改和添加,对于post请求放在请求体body中的参数该方法无能为力;
- 还有一点很重要:如果你重写了HttpServletRequestWrapper中的获取参数的方法,并且过滤器过滤所有请求路径的话,那么你的项目中用post方式的api无法通过@RequestParam接收放在请求体中的参数,如下图:
上面这个接口在重写了HttpServletRequestWrapper中的参数获取方法后就不能使用了,会报异常如下:
原因是这里并没有使用spring自带的方法来获取参数而是使用自定义的方法来获取的参数,而自定义的方法并没有实现对body中数据的处理。
四、过滤器如何拿到请求体中的参数
在Xss防御类中其实也已经实现了获取请求提的方法,就是通过ServletRequest获取其中的输入流,然后解码出请求体中的数据,下面是具体的代码:
/**
* 获取请求Body
* @param request
* @return
*/
public String getBody(HttpServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
这里也有一些要注意的点:
- 通过这个方法只能拿到前端传过来的json格式的数据,但无法接收form-data格式的数据,form-data传递的数据可以通过ServletRequest中自带的获取参数的方法获得。