在系統開發中,與用戶交互的地方,例如表單輸入,瀏覽器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" ,則該過濾器如果獲取用戶輸入,需要自行驗證,當然一個系統中有文件上傳的地方畢竟不多!所以造成的麻煩是很小的!
文章原創,轉載請以鏈接方式著名出處!
【原創】運用裝飾模式截取用戶輸入構建通用驗證
posted on 2009-11-24 12:15
零全零美 閱讀(1227)
評論(2) 編輯 收藏 所屬分類:
設計模式