作者:EasyJF開(kāi)源團(tuán)隊(duì) 大峽
一、簡(jiǎn)介
在Java Web應(yīng)用程中,特別是網(wǎng)站開(kāi)發(fā)中,我們有時(shí)候需要為應(yīng)用程序增加一個(gè)入侵檢測(cè)程序來(lái)防止惡意刷新的功能,防止非法用戶不斷的往Web應(yīng)用中重復(fù)發(fā)送數(shù)據(jù)。當(dāng)然,入侵檢測(cè)可以用很多方法實(shí)現(xiàn),包括軟件、硬件防火墻,入侵檢測(cè)的策略也很多。在這里我們主要介紹的是Java Web應(yīng)用程序中通過(guò)軟件的方式實(shí)現(xiàn)簡(jiǎn)單的入侵檢測(cè)及防御。
該方法的實(shí)現(xiàn)原理很簡(jiǎn)單,就是用戶訪問(wèn)Web系統(tǒng)時(shí)記錄每個(gè)用戶的信息,然后進(jìn)行對(duì)照,并根據(jù)設(shè)定的策略(比如:1秒鐘刷新頁(yè)面10次)判斷用戶是否屬于惡意刷新。
我們的入侵檢測(cè)程序應(yīng)該放到所有Java Web程序的執(zhí)行前,也即若發(fā)現(xiàn)用戶是惡意刷新就不再繼續(xù)執(zhí)行Java Web中的其它部分內(nèi)容,否則就會(huì)失去了意義。這就需要以插件的方式把入侵檢測(cè)的程序置入Java Web應(yīng)用中,使得每次用戶訪問(wèn)Java Web,都先要到這個(gè)入侵檢測(cè)程序中報(bào)一次到,符合規(guī)則才能放行。
Java Web應(yīng)用大致分為兩種,一種純JSP(+Java Bean)方式,一種是基于框架(如Struts、EasyJWeb等)的。第一種方式的Java Web可以通過(guò)Java Servlet中的Filter接口實(shí)現(xiàn),也即實(shí)現(xiàn)一個(gè)Filter接口,在其doFilter方法中插入入侵檢測(cè)程序,然后再web.xml中作簡(jiǎn)單的配置即可。在基于框架的Web應(yīng)用中,由于所有應(yīng)用都有一個(gè)入口,因此可以把入侵檢測(cè)的程序直接插入框架入口引擎中,使框架本身支持入侵檢測(cè)功能。當(dāng)然,也可以通過(guò)實(shí)現(xiàn)Filter接口來(lái)實(shí)現(xiàn)。
在EasyJWeb框架中,已經(jīng)置入了簡(jiǎn)單入侵檢測(cè)的程序,因此,這里我們以EasyJWeb框架為例,介紹具體的實(shí)現(xiàn)方法及源碼,完整的代碼可以在EasyJWeb源碼中找到。
在基于EasyJWeb的Java Web應(yīng)用中(如http://www.easyjf.com/bbs/),默認(rèn)情況下你只要連續(xù)刷新頁(yè)面次數(shù)過(guò)多,即會(huì)彈出如下的錯(cuò)誤:
EasyJWeb框架友情提示!:-):
您對(duì)頁(yè)面的刷新太快,請(qǐng)等待60秒后再刷新頁(yè)面!
詳細(xì)請(qǐng)查詢http://www.easyjf.com
二、用戶訪問(wèn)信息記錄UserConnect.java類(lèi)
這個(gè)類(lèi)是一個(gè)簡(jiǎn)單的Java Bean,主要代表用戶的信息,包括用戶名、IP、第一次訪問(wèn)時(shí)間、最后登錄時(shí)間、登錄次數(shù)、用戶狀態(tài)等。全部
代碼如下:
package com.easyjf.web;
import java.util.Date;
/**
*
* <p>Title:用戶驗(yàn)證信息</p>
* <p>Description:記錄用戶登錄信息,判斷用戶登錄情況</p>
* <p>Copyright: Copyright (c) 2006</p>
* <p>Company: www.easyjf.com</p>
* @author 蔡世友
* @version 1.0
*/
public class UserConnect {
private String userName;
private String ip;
private Date firstFailureTime;
private Date lastLoginTime;
private int failureTimes;//用戶登錄失敗次數(shù)
private int status=0;//用戶狀態(tài)0表示正常,-1表示鎖定
public int getFailureTimes() {
?return failureTimes;
}
public void setFailureTimes(int failureTimes) {
?this.failureTimes = failureTimes;
}
public Date getFirstFailureTime() {
?return firstFailureTime;
}
public void setFirstFailureTime(Date firstFailureTime) {
?this.firstFailureTime = firstFailureTime;
}
public String getIp() {
?return ip;
}
public void setIp(String ip) {
?this.ip = ip;
}
public Date getLastLoginTime() {
?return lastLoginTime;
}
public void setLastLoginTime(Date lastLoginTime) {
?this.lastLoginTime = lastLoginTime;
}
public String getUserName() {
?return userName;
}
public void setUserName(String userName) {
?this.userName = userName;
}
public int getStatus() {
?return status;
}
public void setStatus(int status) {
?this.status = status;
}
}
三、監(jiān)控線程UserConnectManage.java類(lèi)
這是入侵檢測(cè)的核心部分,主要實(shí)現(xiàn)具體的入侵檢測(cè)、記錄、判斷用戶信息、在線用戶的刷新等功能,并提供其它應(yīng)用程序使用本組件的調(diào)用接口。
package com.easyjf.web;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
/**
*
* <p>Title:用戶入侵檢測(cè)信息</p>
* <p>Description:用于判斷用戶刷新情況檢查,默認(rèn)為10秒鐘之內(nèi)連續(xù)連接10次為超時(shí)</p>
* <p>Copyright: Copyright (c) 2006</p>
* <p>Company: www.easyjf.com</p>
* @author 蔡世友
* @version 1.0
*/
public class UserConnectManage {
private static final Logger logger = (Logger) Logger.getLogger(UserConnectManage.class.getName());
private static int maxFailureTimes=10;//最大登錄失敗次數(shù)
private static long maxFailureInterval=10000;//毫秒,達(dá)到最大登錄次數(shù)且在這個(gè)時(shí)間范圍內(nèi)
private static long waitInterval=60000;//失敗后接受連接的等待時(shí)間,默認(rèn)1分鐘
private static int maxOnlineUser=200;//同時(shí)在線的最大數(shù)
private final static Map users=new HashMap();//使用ip+userName為key存放用戶登錄信息UserLoginAuth
private static Thread checkThread=null;
private static class CheckTimeOut implements Runnable{?
private Thread parentThread;
public? CheckTimeOut(Thread parentThread)?
?{
??this.parentThread=parentThread;?
??synchronized(this){
??if(checkThread==null){???
???checkThread= new Thread(this);
???//System.out.println("創(chuàng)建一個(gè)新線程!");
???checkThread.start();???
???? }
??}
?}?
?public void run() {??
??while(true)
??{
???if(parentThread.isAlive()){
???try{
???Thread.sleep(2000);
???int i=0;
???if(users.size()>maxOnlineUser)//當(dāng)達(dá)到最大用戶數(shù)時(shí)清除
???{
????synchronized(users){//執(zhí)行刪除操作
????Iterator it=users.keySet().iterator();
????Set set=new HashSet();
????Date now=new Date();
????while(it.hasNext())
????{
?????Object key=it.next();
?????UserConnect user=(UserConnect)users.get(key);
?????if(now.getTime()-user.getFirstFailureTime().getTime()>maxFailureInterval)//刪除超時(shí)的用戶
?????{??????
??????set.add(key);
??????logger.info("刪除了一個(gè)超時(shí)的連接"+i);
??????i++;
?????}
????}
????if(i<5)//如果刪除少于5個(gè),則強(qiáng)行刪除1/2在線記錄,犧牲性能的情況下保證內(nèi)存
????{
?????int num=maxOnlineUser/2;
?????it=users.keySet().iterator();
?????while(it.hasNext() && i<num)
?????{??????
??????set.add(it.next());
??????logger.info("刪除了一個(gè)多余的連接"+i);
??????i++;
?????}
????}
????users.keySet().removeAll(set);
????}
???}
???
???}
???catch(Exception e)
???{
????e.printStackTrace();
???}
???
??}
???else
???{???
???break;
???}
??}
??logger.info("監(jiān)視程序運(yùn)行結(jié)束!");?
?}
}
//通過(guò)checkLoginValidate判斷是否合法的登錄連接,如果合法則繼續(xù),非法則執(zhí)行
public static boolean checkLoginValidate(String ip,String userName)//只檢查認(rèn)證失敗次數(shù)
{
?boolean ret=true;
?Date now=new Date();?
?String key=ip+":"+userName;
?UserConnect auth=(UserConnect)users.get(key);
?if(auth==null)//把用戶當(dāng)前的訪問(wèn)信息加入到users容器中
?{
??auth=new UserConnect();
??auth.setIp(ip);
??auth.setUserName(userName);
??auth.setFailureTimes(0);
??auth.setFirstFailureTime(now);
??users.put(key,auth);??
??if(checkThread==null)new CheckTimeOut(Thread.currentThread());
?}?
?else
?{
??if(auth.getFailureTimes()>maxFailureTimes)
??{
?????????? //如果在限定的時(shí)間間隔內(nèi),則返回拒絕用戶連接的信息
???if((now.getTime()-auth.getFirstFailureTime().getTime())<maxFailureInterval)
???{
????ret=false;
????auth.setStatus(-1);
???}
???else? if(auth.getStatus()==-1 && (now.getTime()-auth.getFirstFailureTime().getTime()<(maxFailureInterval+waitInterval)))//重置計(jì)數(shù)器
???{
????ret=false;
???}
???else????
???{????
????auth.setFailureTimes(0);
????auth.setFirstFailureTime(now);
????auth.setStatus(0);
???}
???
??}
??//登錄次數(shù)加1
??auth.setFailureTimes(auth.getFailureTimes()+1);
?}
?//System.out.println(key+":"+auth.getFailureTimes()+":"+ret+":"+(now.getTime()-auth.getFirstFailureTime().getTime()));
?return ret;
}
public static void reset(String ip,String userName)//重置用戶信息
{?
?Date now=new Date();?
?String key=ip+":"+userName;
?UserConnect auth=(UserConnect)users.get(key);
?if(auth==null)//把用戶當(dāng)前的訪問(wèn)信息加入到users容器中
?{
??auth=new UserConnect();
??auth.setIp(ip);
??auth.setUserName(userName);
??auth.setFailureTimes(0);
??auth.setFirstFailureTime(now);
??users.put(key,auth);
?}?
?else
?{
??auth.setFailureTimes(0);
??auth.setFirstFailureTime(now);
?}
}
public static void remove(String ip,String userName)//刪除用戶在容器中的記錄
{
?String key=ip+":"+userName;
?users.remove(key);
}
public static void clear()//清空容器中內(nèi)容
{
?if(!users.isEmpty())users.clear();
}
public static long getMaxFailureInterval() {
?return maxFailureInterval;
}
public static void setMaxFailureInterval(long maxFailureInterval) {
?UserConnectManage.maxFailureInterval = maxFailureInterval;
}
public static int getMaxFailureTimes() {
?return maxFailureTimes;
}
public static void setMaxFailureTimes(int maxFailureTimes) {
?UserConnectManage.maxFailureTimes = maxFailureTimes;
}
public static int getMaxOnlineUser() {
?return maxOnlineUser;
}
public static void setMaxOnlineUser(int maxOnlineUser) {
?UserConnectManage.maxOnlineUser = maxOnlineUser;
}
public static long getWaitInterval() {
?return waitInterval;
}
public static void setWaitInterval(long waitInterval) {
?UserConnectManage.waitInterval = waitInterval;
}
四、調(diào)用接口
在需要進(jìn)入侵檢測(cè)判斷的地方,直接使用UserConnectManage類(lèi)中的checkLoginValidate方法即可。如EasyJWeb的核心Servlet
com.easyjf.web.ActionServlet中調(diào)用UserConnectManage的代碼:
? ?if(!UserConnectManage.checkLoginValidate(request.getRemoteAddr(),"guest"))
? ? ? ?{ ? ? ? ? ? ?
? ? ? ? ? ?info(request,response,new Exception("您對(duì)頁(yè)面的刷新太快,請(qǐng)等待"+UserConnectManage.getWaitInterval()/1000+"秒
后再刷新頁(yè)面!"));
? ? ? ? ? ?return;
? ? ? ?}??????
五、總結(jié)
當(dāng)然,這里提供的方法只是一個(gè)簡(jiǎn)單的實(shí)現(xiàn)示例,由于上面的用戶信息是直接保存在內(nèi)存中,若并發(fā)用戶很大的時(shí)候的代碼的占用,可以考慮引入數(shù)據(jù)庫(kù)來(lái)記錄用戶的訪問(wèn)信息,當(dāng)然相應(yīng)的執(zhí)行效率肯定用降低。上面介紹的實(shí)現(xiàn)中,入侵檢測(cè)判斷的策略也只有用戶訪問(wèn)次數(shù)及時(shí)間間隔兩個(gè)元素,您還可以根據(jù)你的實(shí)現(xiàn)情況增加其它的檢測(cè)元素。
由于水平有限,該設(shè)計(jì)上有N不合理或者需要改進(jìn)的地方,懇請(qǐng)大家指正!