运用装饰模式截取用户输入构建通用验证
?在系统开发中,与用户交互的地方,例如表单输入,浏览器URL传参都是系统安全的敏感地带。传统的客户端JavaScript验证只能挡君子而不能拦小人,因为用户一旦将JS禁用,我们就无能为力。于是人们说最安全的方式还是在服务器端验证。但是这种最安全的方式却是很麻烦的做法!因为我们无法只单单的在服务器端验证,我们还需要做客户端传统验证,这样一来同一套验证,客户端一次,服务器端一次,暂且不考虑执行的效率,单单是开发的效率就很让人抓狂了!尤其是对URL传参的验证,难道我们对每一个URL传递参数的地方都必须写一堆的验证代码吗?
?如果有这样一种方法能够通过一个过滤器能够一次性的拦截获取所有的用户输入,那么我们就可以只通过这个过滤器来做基础的安全性验证,例如我们可以过滤SQL语句,过滤非安全字符等等,而把业务规则验证留给程序员去实现,就将大大的加快开发效率,同时也可以构建一个通用的用户输入验证框架,减少与程序的紧耦合!
?例如我们将所有用户输入中的"<"改为"<",将所有的">"改为">"
?本文试图寻找一种方法来解决这个问题!
?
?关于对装饰模式的具体说明,可以自行Google一下,或者可以查看此文:装饰Servlet Request对象,建议想了解原理的读者先阅读一下这篇文章!
?首先我们创建一个filter,让它可以拦截所有的请求!
<filter> <filter-name>userInputFilter</filter-name> <filter-class> com.djwl.core.security.UserInputFilter </filter-class> </filter> <filter-mapping> <filter-name>userInputFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
?
package com.djwl.core.security;import java.io.IOException;import javax.servlet.FilterChain;import javax.servlet.FilterConfig;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;/** *//** * 功能描述:过滤用户输入的危险字符,及SQL语句<BR> * @author 杨凯 <BR> * 时间:Jun 9, 2009 1:22:03 PM <BR> */public class UserInputFilter implements javax.servlet.Filter { public void destroy() { } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)servletRequest; request.setCharacterEncoding("gbk"); //重点是这句,该处我们运用装饰模式构建一个自己的ServletRequest类 chain.doFilter(new UserInputFilterHttpServletRequestWrapper(request), servletResponse); } public void init(FilterConfig filterConfig) throws ServletException { }}?
package com.djwl.core.security;import java.util.HashMap;import java.util.Map;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletRequestWrapper;import com.djwl.core.utils.V;public final class UserInputFilterHttpServletRequestWrapper extends HttpServletRequestWrapper { public final static Map characterMap = new HashMap(); //对于一些我们不想让该类验证的值,例如我使用Tapestry开发,那么这些Tapestry框架自己的东西,我们忽略掉! static{ characterMap.put("formids", ""); characterMap.put("seedids", ""); characterMap.put("submitmode", ""); characterMap.put("sp", ""); } //构造函数 public UserInputFilterHttpServletRequestWrapper(HttpServletRequest request) { super(request); } //验证从页面上提取单个值的情况,包括URL传参 @Override public String getParameter(String name) { return V.validate(super.getParameter(name)); } //验证从页面上一次性获取多个值的情况 @Override public String[] getParameterValues(String name) { //忽略一些我们的设定 if (characterMap.containsKey(name)) { return super.getParameterValues(name); } String[] userinputs = super.getParameterValues(name); if (userinputs == null) { return null; } //逐一判断 String[] results = new String[userinputs.length]; int i=0; for (String string : userinputs) { results[i] = V.validate(string); i++; } return results; }}?
package com.djwl.core.utils;import java.util.regex.Matcher;import java.util.regex.Pattern;import org.apache.commons.lang.StringUtils;import com.djwl.core.MisException;public class V { /** *//** * 功能描述:改变用户输入<BR> * @param str * @return * @author:杨凯<BR> * 时间:Nov 24, 2009 12:07:57 PM<BR> */ private static String escape(String str){// str = StringEscapeUtils.escapeSql(str);// str = StringEscapeUtils.escapeHtml(str);// //str = StringEscapeUtils.escapeJavaScript(str);// str = str.replaceAll("#", ""); str = str.replaceAll("<", "<").replaceAll(">", ">"); str = str.replaceAll("\r\n", "<BR>"); str = str.replaceAll("null", " "); return str; } /** *//** * 功能描述:截取危险字符<BR> * @param str * @return * @author:杨凯<BR> * 时间:Nov 24, 2009 12:08:08 PM<BR> */ private static Boolean contains(String str){ //这里我们可以根据自己的逻辑,编写适当的正则表达式判断 String regexp = "\\b(drop|delete|update|insert|select|call|exec|set|declare|script|link)\\b"; Matcher matcher = Pattern.compile(regexp).matcher(str.toLowerCase()); if (matcher.find()) { throw new MisException("用户输入中含有非法字符"); } else { return true; } } /** *//** * 功能描述:验证字符串<BR> * @param str * @return * @author:杨凯<BR> * 时间:Jun 9, 2009 5:09:31 PM<BR> */ public static String validate(String str){ if (StringUtils.isNotBlank(str) && contains(str)) { return escape(str); } return null; } }??????需要说明的一点是,如果表单中有文件上传的控件,意思是说如果from标签中enctype="multipart/form-data"?,则该过滤器如果获取用户输入,需要自行验证,当然一个系统中有文件上传的地方毕竟不多!所以造成的麻烦是很小的!