在实际的java web开发过程中,经常会遇到需要对ServletRequest对象内容进行过滤的情况,有两种可选方法:在需要引用ServletRequest对象内容的地方进行内容过滤;添加过滤器对所有ServletRequest对象内容进行过滤。前者实现简单,但维护起来十分麻烦,程序冗余严重;后者一劳永逸,但由于ServletRequest对象的内容不能直接被修改,所以,需要重新构造ServletRequest对象内容。本文描述了一种在实际应用当中,对web请求进行过滤的方法。从添加过滤器到实施过程中遇到的各种问题,再到最终满足需求的程序完成。并将解决各种问题的参考链接加入到了文中。
开发过滤ServletRequest内容的类
由于ServletRequest对象的内容不能直接被修改,所以,需要重新构造ServletRequest对象内容,通常采用的做法是:新建一个类继承HttpServletRequestWrapper,并添加构造函数如下:
public class SecurityHttpServletRequestWrapper extends HttpServletRequestWrapper {
public SecurityHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
接下来,就是要对其内容进行过滤了,在对经常使用到的getParameter()方法结果进行过滤,最简单的方式是在SecurityHttpServletRequestWrapper 类中添加 private Map
@Override
public String getParameter(String name) {
String[] values = params.get(name);
if(values == null || values.length == 0) return null;
return values[0];
}
@Override
public Map<String, String[]> getParameterMap() {
return params;
}
@Override
public String[] getParameterValues(String name) {
return params.get(name);
}
这里参考了:https://blog.csdn.net/tyyytcj/article/details/78528499
本以为到这里就完美结束了,但是,实际并非如此!现象:在系统中使用了spring @RequestBody注解的地方,未能获得页面所发送的请求内容。
经过一番源码查看,终于发现:@RequestBody注解调用了ServletRequest的getInputStream()方法。
那又是为什么使用getInputStream()就获取不到内容呢?
归根结底,程序在获取请求内容的时候,根据不同场景会使用到request.getParameter()、request.getInputStream()和request.getReader()这三者中的其中一个,而这三种方法是有冲突的,因为流只能被读一次,具体原因参考:https://www.cnblogs.com/v5hanhan/p/5646054.html
那么知道原因之后,我们现在要做的就是,重写getInputStream()方法,使之能够返回有效内容。此处参考:https://www.cnblogs.com/bigVGod/p/7240778.html
实践中还发现中文编码存在问题,解决方案参考:https://www.zhihu.com/question/35728721
调整之后,新的构造函数代码如下:
// request.getParameter()、request.getInputStream()和request.getReader()这三种方法是有冲突的,
// 原因是InputStream内容只能被读取一次,
// 所以需要针对三种方法分别制作有效返回内容。其中request.getReader()被用到的情况比较少,暂未制作对应返回。
private ServletInputStream servletInputStream;
private Map<String, String[]> params = new HashMap<String, String[]>();
public SecurityHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
String finalStr = null;
try {
byte[] originBytes = StreamUtils.copyToByteArray(request.getInputStream());
//此处对内容进行过滤
String safeStr = clearThreatChar(new String(originBytes));
//字符串解码,如果不执行此操作,中文将无法正常显示
finalStr = URLDecoder.decode(safeStr,"utf-8");
} catch (IOException e) {
e.printStackTrace();
}
//获取请求方式为get的请求参数,此类参数无法从InputStream中获取到。
params.putAll(request.getParameterMap());
//从InputStream中解析参数,并添加至params中。
for(String k:finalStr.split("&")){
int index = k.indexOf("=");
if(index > 0){
String key = k.split("=")[0];
String value = "";
if((index+1) < k.length()){
value = k.substring(index+1);
}
params.put(key,new String[]{value});
}
}
byte[] requestBody = finalStr.getBytes();
if(requestBody == null) requestBody = new byte[0];
servletInputStream = generateInputStream(new ByteArrayInputStream(requestBody));
}
另外关于request.getReader()相关方法重写参考:http://hae.iteye.com/blog/2175117
完整的代码由于有一百多行,就不贴在这里了,可以前往(https://download.csdn.net/download/chenjhit/10357697)下载,顺便挣两个积分,或者加我的QQ,我直接发送给你们!
开发Filter过滤器
这里直接贴代码,主要就是需要跳过负载过大的请求,避免内存溢出问题。
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Created by Administrator on 18-4-16.
*/
public class SecurityFilter implements Filter {
// 用于排除不用进行安全过滤的请求;
// 原因是由于部分请求内容过大,会导致溢出异常;
// 使用Map的理由是:不使用for循环,节省时间。
Map<String,String> excludeRequestMap = new HashMap<String,String>(){
{
put("upload",""); //请求示例
}
};
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String path = ((HttpServletRequest)servletRequest).getServletPath();
if(excludeRequestMap.containsKey(path)){
filterChain.doFilter(servletRequest,servletResponse);
}else {
filterChain.doFilter(new SecurityHttpServletRequestWrapper((HttpServletRequest)servletRequest),servletResponse);
}
}
public void destroy() {
}
}
安全过滤
跨站脚本攻击(XSS)过滤的两种方法:https://blog.csdn.net/jwdstef/article/details/42082427
SQL注入过滤:https://blog.csdn.net/chenjhit/article/details/78253216
拓展思考
其实过滤还可以利用spring 的aop,在切面中进行过滤,可以跳过许多不必要的请求过滤,减少系统资源占用,但是由于原有系统代码凌乱,不敢轻易放过任何一个请求,所以还是写在了filter里,大家可以尝试在aop中实现,整体思路是差不多的。
==================================
==疑问?帮助?批评?欢迎评论 | QQ:593159978==
==================================