Dict.CN 在线词典, 英语学习, 在线翻译

都市淘沙者

荔枝FM Everyone can be host

统计

留言簿(23)

积分与排名

优秀学习网站

友情连接

阅读排行榜

评论排行榜

防止恶意刷新页面的Java实现(zhuantie)

防止恶意刷新页面的Java实现

在很多对安全性要求较高的项目中,需要提供至少一种对整个项目的安全控制方案,常用的比如身份认证、访问控制、安全审计等等。由于设计不合理而导致的安全问题可能会给项目带来非常大的隐患,正是因为如此,安全问题也得到了广大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过滤器。以下是具体的代码实现,通过代码中的注释,可以清楚看到我们的实现思路。
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();//关闭数据库连接
}
下面,我们来着重讨论如何实现恶意刷新的过滤器。因为我们的需求是在任何的10秒中记录请求数而不是在以10秒为一个时间段,那么就需要保证时间的连续性,鉴于此需求,我们需要保存用户连续10次请求的时间,如果其最后一次的请求时间与第一次的时间差小于10秒并且次数已经达到10次,则违背设定规则,我们设计了下面的类来保存用户的访问时间序列。

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;
}

public void insert(long nextTime) ...{
if (this.getLast() != 0)//数组已经满了
...{
//去掉首元素,将数组元素顺序前移,nextTime插到最后
for(int i = 0 ;i < this.length-1;i++) ...{
time = 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;
}
}


    该类中采用了访问者的会话ID来标识来自客户端的请求,读者可以很方便地修改该标识,比如修改为注册会员的用户名,这样,就可以实现对会员的恶意访问的屏蔽,同时这样的好处还在于,可以屏弃对session和cookies的依赖,因为如果用户浏览器如果禁用cookie,通常利用session和cookies实现的恶意刷新就失去了作用。虽然,我们这样做也可能是一种消耗内存的方式,但是,的确是一种值得采用并进行优化的折中方案。
最后,也是最关键的就是我们如何来通过过滤器实现对恶意用户的请求进行屏蔽。下面是该过滤器的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);//默认最大访问次数


   该方案的实现中采用了保存时间序列的方案,而这样,就要在内存中开辟一个可变可控的数组,在一定程度上浪费了资源,但是,也提供了在保证连续时间下防止恶意刷新的功能,并且可以屏弃对session和cookies的依赖,达到完全的可自控,保证了有效性。同时,提供了IP过滤机制来保证功能体系的完善和有效,目前该方案在项目中应用良好。如果读者对此有更加适合的解

posted on 2007-10-10 14:17 都市淘沙者 阅读(1125) 评论(0)  编辑  收藏 所属分类: Java Basic/Lucene/开源资料


只有注册用户登录后才能发表评论。


网站导航: