憨厚生

          ----Java's Slave----
          ***Java's Host***

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            165 隨筆 :: 17 文章 :: 90 評論 :: 0 Trackbacks
          轉(zhuǎn) http://blog.csdn.net/zhaowei001/archive/2007/12/29/2001800.aspx

          作者:EasyJF開源團(tuán)隊 大峽

          一、簡介

          在Java Web應(yīng)用程中,特別是網(wǎng)站開發(fā)中,我們有時候需要為應(yīng)用程序增加一個入侵檢測程序來防止惡意刷新的功能,防止非法用戶不斷的往Web應(yīng)用中重復(fù)發(fā)送數(shù) 據(jù)。當(dāng)然,入侵檢測可以用很多方法實(shí)現(xiàn),包括軟件、硬件防火墻,入侵檢測的策略也很多。在這里我們主要介紹的是Java Web應(yīng)用程序中通過軟件的方式實(shí)現(xiàn)簡單的入侵檢測及防御。

            該方法的實(shí)現(xiàn)原理很簡單,就是用戶訪問Web系統(tǒng)時記錄每個用戶的信息,然后進(jìn)行對照,并根據(jù)設(shè)定的策略(比如:1秒鐘刷新頁面10次)判斷用戶是否屬于惡意刷新。

          我們的入侵檢測程序應(yīng)該放到所有Java Web程序的執(zhí)行前,也即若發(fā)現(xiàn)用戶是惡意刷新就不再繼續(xù)執(zhí)行Java Web中的其它部分內(nèi)容,否則就會失去了意義。這就需要以插件的方式把入侵檢測的程序置入Java Web應(yīng)用中,使得每次用戶訪問Java Web,都先要到這個入侵檢測程序中報一次到,符合規(guī)則才能放行。

            Java Web應(yīng)用大致分為兩種,一種純JSP(+Java Bean)方式,一種是基于框架(如Struts、EasyJWeb等)的。第一種方式的Java Web可以通過Java Servlet中的Filter接口實(shí)現(xiàn),也即實(shí)現(xiàn)一個Filter接口,在其doFilter方法中插入入侵檢測程序,然后再web.xml中作簡單的 配置即可。在基于框架的Web應(yīng)用中,由于所有應(yīng)用都有一個入口,因此可以把入侵檢測的程序直接插入框架入口引擎中,使框架本身支持入侵檢測功能。當(dāng)然, 也可以通過實(shí)現(xiàn)Filter接口來實(shí)現(xiàn)。

            在EasyJWeb框架中,已經(jīng)置入了簡單入侵檢測的程序,因此,這里我們以EasyJWeb框架為例,介紹具體的實(shí)現(xiàn)方法及源碼,完整的代碼可以在EasyJWeb源碼中找到。

            在基于EasyJWeb的Java Web應(yīng)用中(如http://www.easyjf.com/bbs/),默認(rèn)情況下你只要連續(xù)刷新頁面次數(shù)過多,即會彈出如下的錯誤:

            EasyJWeb框架友情提示!:-):
            您對頁面的刷新太快,請等待60秒后再刷新頁面!
          詳細(xì)請查詢http://www.easyjf.com


          二、用戶訪問信息記錄UserConnect.java類  

          這個類是一個簡單的Java Bean,主要代表用戶的信息,包括用戶名、IP、第一次訪問時間、最后登錄時間、登錄次數(shù)、用戶狀態(tài)等。全部

          代碼如下:

          package com.easyjf.web;

          import java.util.Date;
          /**
          *
          *

          Title:用戶驗證信息


          *

          Description:記錄用戶登錄信息,判斷用戶登錄情況


          *

          Copyright: Copyright (c) 2006


          *

          Company: www.easyjf.com

          >
          * @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類

          這是入侵檢測的核心部分,主要實(shí)現(xiàn)具體的入侵檢測、記錄、判斷用戶信息、在線用戶的刷新等功能,并提供其它應(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;
          /**
          *
          *

          Title:用戶入侵檢測信息


          *

          Description:用于判斷用戶刷新情況檢查,默認(rèn)為10秒鐘之內(nèi)連續(xù)連接10次為超時


          *

          Copyright: Copyright (c) 2006


          *

          Company: www.easyjf.com

          >
          * @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ù)且在這個時間范圍內(nèi)
          private static long waitInterval=60000;//失敗后接受連接的等待時間,默認(rèn)1分鐘
          private static int maxOnlineUser=200;//同時在線的最大數(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)建一個新線程!");
             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ù)時清除
             {
              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)//刪除超時的用戶
               {      
                set.add(key);
                logger.info("刪除了一個超時的連接"+i);
                i++;
               }
              }
              if(i<5)//如果刪除少于5個,則強(qiáng)行刪除1/2在線記錄,犧牲性能的情況下保證內(nèi)存
              {
               int num=maxOnlineUser/2;
               it=users.keySet().iterator();
               while(it.hasNext() && i
               {      
                set.add(it.next());
                logger.info("刪除了一個多余的連接"+i);
                i++;
               }
              }
              users.keySet().removeAll(set);
              }
             }
             
             }
             catch(Exception e)
             {
              e.printStackTrace();
             }
             
            }
             else
             {   
             break;
             }
            }
            logger.info("監(jiān)視程序運(yùn)行結(jié)束!"); 
           }
          }
          //通過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)前的訪問信息加入到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)
            {
                     //如果在限定的時間間隔內(nèi),則返回拒絕用戶連接的信息
             if((now.getTime()-auth.getFirstFailureTime().getTime())
             {
              ret=false;
              auth.setStatus(-1);
             }
             else  if(auth.getStatus()==-1 && (now.getTime()-auth.getFirstFailureTime().getTime()< (maxFailureInterval+waitInterval)))//重置計數(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)前的訪問信息加入到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)入侵檢測判斷的地方,直接使用UserConnectManage類中的checkLoginValidate方法即可。如EasyJWeb的核心Servlet 

          com.easyjf.web.ActionServlet中調(diào)用UserConnectManage的代碼:
             if(!UserConnectManage.checkLoginValidate(request.getRemoteAddr(),"guest"))
                 {            
                     info(request,response,new Exception("您對頁面的刷新太快,請等待"+UserConnectManage.getWaitInterval()/1000+"秒

          后再刷新頁面!"));
                     return;
                 }      



          五、總結(jié)
          當(dāng)然,這里提供的方法只是一個簡單的實(shí)現(xiàn)示例,由于上面的用戶信息是直接保存在內(nèi)存中,若并發(fā)用戶很大的時候的代碼的占用,可以考慮引入數(shù)據(jù)庫來記錄用 戶的訪問信息,當(dāng)然相應(yīng)的執(zhí)行效率肯定用降低。上面介紹的實(shí)現(xiàn)中,入侵檢測判斷的策略也只有用戶訪問次數(shù)及時間間隔兩個元素,您還可以根據(jù)你的實(shí)現(xiàn)情況增 加其它的檢測元素。

          由于水平有限,該設(shè)計上有N不合理或者需要改進(jìn)的地方,懇請大家指正!



          Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=657199



          posted on 2009-04-17 16:42 二胡 閱讀(220) 評論(0)  編輯  收藏 所屬分類: Java 、web系統(tǒng)開發(fā)
          主站蜘蛛池模板: 安顺市| 大埔区| 嘉黎县| 松江区| 霍邱县| 孝义市| 赞皇县| 花莲县| 桦川县| 防城港市| 五大连池市| 英吉沙县| 营口市| 乌拉特后旗| 和田县| 霍林郭勒市| 北流市| 略阳县| 汉阴县| 青川县| 黄石市| 文成县| 沭阳县| 新竹市| 宜州市| 江山市| 靖边县| 邢台县| 兴义市| 齐齐哈尔市| 宜君县| 南靖县| 巧家县| 万安县| 永康市| 开封市| 东方市| 化隆| 右玉县| 贺州市| 常宁市|