转载:http://www.pcdog.com/edu/java/05/02/b316943.html
在很多对安全性要求较高的项目中,需要提供至少一种对整个项目的安全控制方案,常用的比如身份认证、访问控制、安全审计等等。由于设计不合理而导致的安全问题可能会给项目带来非常大的隐患,正是因为如此,安全问题也得到了广大web项目开发者的重视,尤其是在电子政务和电子商务的开发中,更是需要提供一定层次上的安全性要求。
本文的重点在于实现一种防止恶意刷新页面的方法,笔者在实现该功能时,查找了很多资料并且进行了多次讨论协商,提出了这种针对特定需求的,独立性较高的方案。
下面,我们来具体说一下客户的需求。
该功能是在某电子政务的安全审计子系统中要求实现的一个功能,因为安全审计子系统涉及多方面的内容,包括外网访问日志,专网和内网办公日志,数据库操作日志等等的控制和查询等等,通过这些功能,可以提供给管理员各种接口,从而可以查看系统的使用状况,并对误操作或者恶意操作进行有效追踪和审计。而外网作为电子政务的门户,是为广大公众提供访问的接口,那么保证其正常浏览是作为电子政务系统中不可或缺的功能需求,因此,实现防止恶意刷新的功能便是提供良好功能保障的众多手段中的一种。
这里所说的恶意刷新并不是指网络上通常所指的如何通过javascript脚本来屏蔽F5键等方案,而是通过过滤机制,由程序实现的一种对某次会话中的恶意访问的控制,该方案中制定的规则是:来自某一客户的访问如果在10秒中内超过10次则被假设为恶意访问。这样的方案也是比较合理的,既避免了恶意用户使用F5键来频繁刷新某一固定页面,也防止了用户漫无目的地乱点页面。同时,该方案还提供了辅助功能,在规则设定方面比较灵活,默认为10次/10秒,这两个参数是可以通过程序来设置的;在恶意控制方面,如果用户的访问违反了该规则,则通过过滤器机制提取该用户的IP地址,将其置为拒绝提供服务的IP列表中,将拒绝再次为来自该IP的请求提供服务。
下面,我们来具体说明一下该方案的实现方法。
首先,从整个方案的实施体系来讲,我们提取出如下的控制流程。
从上图可以看出,来自客户端的请求首先要经过IP过滤器的过滤,只有不在恶意IP列表中的IP地址的请求才有可能被响应,然后还要经过恶意刷新过滤器的验证才能得到服务器的最终响应。如果该IP在10秒内连续请求的次数达到了10次,那么,它将被记入到恶意IP列表中,将不会通过IP过滤器的验证,不会得到服务器的响应,从而,我们就实现了对恶意用户的过滤。当然,通过IP过滤器的作用,我们还可以将某些IP直接列入黑名单中,比如某些具有攻击性的网站的IP地址或者曾经通过网络入侵检测软件的诊断,将危险IP也加入黑名单,这样,可以提高我们的应用的安全性和可用性。
接下来,我们通过代码来看一下如何具体实现我们的功能。通过上面的流程图,我们可以清晰看到,在应用中,我们配置了两个过滤器(关于过滤器的原理和实现,请读者参考相关资料),当然,我们也可以将其写在一个过滤器的doFilter()方法中。
本文方案的实现中采用了两个过滤器,下面我们来简单看一下IP过滤器。以下是具体的代码实现,通过代码中的注释,可以清楚看到我们的实现思路。
从上图可以看出,来自客户端的请求首先要经过IP过滤器的过滤,只有不在恶意IP列表中的IP地址的请求才有可能被响应,然后还要经过恶意刷新过滤器的验证才能得到服务器的最终响应。如果该IP在10秒内连续请求的次数达到了10次,那么,它将被记入到恶意IP列表中,将不会通过IP过滤器的验证,不会得到服务器的响应,从而,我们就实现了对恶意用户的过滤。当然,通过IP过滤器的作用,我们还可以将某些IP直接列入黑名单中,比如某些具有攻击性的网站的IP地址或者曾经通过网络入侵检测软件的诊断,将危险IP也加入黑名单,这样,可以提高我们的应用的安全性和可用性。
接下来,我们通过代码来看一下如何具体实现我们的功能。通过上面的流程图,我们可以清晰看到,在应用中,我们配置了两个过滤器(关于过滤器的原理和实现,请读者参考相关资料),当然,我们也可以将其写在一个过滤器的doFilter()方法中。
本文方案的实现中采用了两个过滤器,下面我们来简单看一下IP过滤器。以下是具体的代码实现,通过代码中的注释,可以清楚看到我们的实现思路。
HttpServletRequest req = (HttpServletRequest)request; HttpServletResponse res = (HttpServletResponse)response; String ip = request.getRemoteAddr(); //得到客户端IP地址 if(!req.getRequestURI().toUpperCase().equals("ERR.JSP")) ...{ IP ipControl = new IP(); //生成一个IP类的事例 try ...{ // getDangerousIP(ip)将查询指定IP是否在IP黑名单的 数据库中 if((ip != null )&&(ipControl.getDangerousIP(ip) !=0)) ...{ res.sendRedirect("/err.jsp?errmsg=ip"); } else ...{ //如果该IP安全,则继续执行 chain.doFilter(request, response); } } catch (ServletException ex) ...{ ex.printStackTrace(); } catch (IOException ex) ...{ ex.printStackTrace(); } catch (Exception ex) ...{ ex.printStackTrace(); } finally...{ ipControl.closeConn();//关闭 数据库连接 }
public class ArrayTime ...{ private long[] time; private int length = 10; //默认为十次(10s内刷新10次则违反规则) public ArrayTime() ...{ } public void init() ...{ time = new long[length]; } public int getLength() ...{ return this.length; } public void setLength(int len) ...{ this.length = len; } public long getLast() ...{ return this.time[length-1]; } public long getFirst() ...{ return this.time[0]; } public long getElement(int i) ...{ return time[i]; } public void insert(long nextTime) ...{ if (this.getLast() != 0)//数组已经满了 ...{ //去掉首元素,将数组元素顺序前移,nextTime插到最后 for(int i = 0 ;i < this.length-1;i++) ...{ time[i] = time[i+1]; } this.time[length-1] = nextTime; } else ...{ //插到下一个,不用排序 int j=0; while(time[j] != 0) ...{ j++; } time[j] = nextTime; } } }
public class Visitor ...{ /**//* Creates a new instance of Visitor *外网访问者,以sessionID作为标识 *违反访问规则将其IP列为受限IP,拒绝访问 */ private String sessionID = null; private ArrayTime requestTimeQueue= new ArrayTime(); public Visitor() ...{ } public void setSessionID(String sessionID) ...{ this.sessionID = sessionID; } public String getSessionID() ...{ return this.sessionID; } public void setRequestTimeQueue(ArrayTime requestTimeQueue) ...{ this.requestTimeQueue = requestTimeQueue; } public ArrayTime getRequestTimeQueue() ...{ return this.requestTimeQueue; } }
最后,也是最关键的就是我们如何来通过过滤器实现对恶意用户的请求进行屏蔽。下面是该过滤器的doFliter()方法的核心部分的实现。
//---------------防止恶意刷新的过滤器-------- HttpServletRequest req = (HttpServletRequest)request; HttpServletResponse res = (HttpServletResponse)response; String sessionID = ((HttpServletRequest)request).getSession().getId();//会话ID Date now =new Date(); Visitor vis = (Visitor) visitors.get(sessionID);//通过sessionID查找访问者, if(vis!= null)//找到访问者,则说明该用户为再次访问 ...{ //小于10秒,但访问超过10次 vis.getRequestTimeQueue().insert(now.getTime());//插入当前请求时间 //得到最后一次和第一次的访问时间差 Long span = vis.getRequestTimeQueue().getLast() - vis.getRequestTimeQueue().getFirst(); if(span < interval && vis.getRequestTimeQueue().getLast() != 0) ...{ //将该用户加入黑名单 IP ip = new IP(); ip.setIP(request.getRemoteAddr()); //得到当前时间 Calendar cal = Calendar.getInstance(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String time=formatter.format(cal.getTime()); ip.setComments("刷新太快,IP已经被封锁"); try ...{ ip.insertIP(); res.sendRedirect("err.jsp?errmsg=refresh"); return; }catch(Exception e) ...{ e.printStackTrace(); }finally...{ ip.closeConn();} } } else ...{ //当前访问者为初次访问 ArrayTime timeQueue = new ArrayTime(); timeQueue.setLength(maxCount); timeQueue.init(); vis=new Visitor(); vis.setSessionID(sessionID); vis.setRequestTimeQueue(timeQueue); vis.getRequestTimeQueue().insert(now.getTime()); visitors.put(sessionID,vis); }
//以下两个值可从文件或 数据库设置,从而达到参数的可设置 private static long interval= GlobalConfig.getInt("refresh.interval",10000); //默认10秒钟 private static int maxCount = GlobalConfig.getInt("refresh.count",10);//默认最大访问次数