`
843977358
  • 浏览: 246768 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

手把手教你自定义IP访问次数限制器 (第二版)

阅读更多

第一版(点击这儿)写的有点冲冲忙忙,也没有考虑到线程安全方面的问题,在这儿先感谢博友cs6641468的建议,考虑线程安全问题,第二版将HashMap改为了线程安全的ConcurrentHashMap(暂留疑),关于cs6641468提到的filterLimitedIpMap方法,修改为了Task定时进行操作(虽然存在误差,但误差很小的话,基本可以忽略不),另外knight_black_bob博友提到用redis,虽然可以解决,但暂时不想再项目中集成redis。

再次感谢博友的建议。

第二版部分代码:

 

监听器MyListener:
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

/**
 * @Description 自定义监听器,项目启动时初始化两个全局的ConcurrentHashMap(线程安全)
 *              ipMap(ip存储器,记录IP的访问次数、访问时间)
 *              limitedIpMap(限制IP存储器)用来存储每个访问用户的IP以及访问的次数
 * @author zhangyd
 * @date 2016年7月28日 下午5:47:23
 * @since JDK : 1.7
 * @version 2.0
 * @modify 改hashMap 为 ConcurrentHashMap
 */
public class MyListener implements ServletContextListener {

	@Override
	public void contextInitialized(ServletContextEvent sce) {
		ServletContext context = sce.getServletContext();
		// IP存储器
		ConcurrentHashMap<String, Long[]> ipMap = new ConcurrentHashMap<String, Long[]>();
		context.setAttribute("ipMap", ipMap);
		// 限制IP存储器:存储被限制的IP信息
		ConcurrentHashMap<String, Long> limitedIpMap = new ConcurrentHashMap<String, Long>();
		context.setAttribute("limitedIpMap", limitedIpMap);
	}

	@Override
	public void contextDestroyed(ServletContextEvent sce) {

	}

}

 

过滤器IPFilter
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.test.util.IPUtil;

/**
 * 
 * @Description 自定义过滤器,用来判断IP访问次数是否超限。<br>
 *              如果前台用户访问网站的频率过快(比如:达到或超过50次/s),则判定该IP为恶意刷新操作,限制该ip访问<br>
 *              默认限制访问时间为1小时,一小时后自定解除限制
 * 
 * @author zhangyd
 * @date 2016年7月28日 下午5:54:51
 * @since JDK : 1.7
 * @version 2.0
 * @modify 改hashMap 为 线程安全的ConcurrentHashMap
 */
public class IPFilter implements Filter {

	/** 默认限制时间(单位:ms) */
	private static final long LIMITED_TIME_MILLIS = 60 * 1000;

	/** 用户连续访问最高阀值,超过该值则认定为恶意操作的IP,进行限制 */
	private static final int LIMIT_NUMBER = 20;

	/** 用户访问最小安全时间,在该时间内如果访问次数大于阀值,则记录为恶意IP,否则视为正常访问 */
	private static final int MIN_SAFE_TIME = 5000;

	private FilterConfig config;

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		this.config = filterConfig;
	}

	/**
	 * @Description 核心处理代码
	 * @param servletRequest
	 * @param servletResponse
	 * @param chain
	 * @throws IOException
	 * @throws ServletException
	 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
	 *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;

		ServletContext context = config.getServletContext();
		// 获取限制IP存储器:存储被限制的IP信息
		ConcurrentHashMap<String, Long> limitedIpMap = (ConcurrentHashMap<String, Long>) context
				.getAttribute("limitedIpMap");
		// 获取用户IP
		String ip = IPUtil.getIp(request);
		// 判断是否是被限制的IP,如果是则跳到异常页面
		if (isLimitedIP(limitedIpMap, ip)) {
			long limitedTime = limitedIpMap.get(ip) - System.currentTimeMillis();
			forward(request, response, ((limitedTime / 1000) + (limitedTime % 1000 > 0 ? 1 : 0)));
			return;
		}
		// 获取IP存储器
		ConcurrentHashMap<String, Long[]> ipMap = (ConcurrentHashMap<String, Long[]>) context.getAttribute("ipMap");

		// 判断存储器中是否存在当前IP,如果没有则为初次访问,初始化该ip
		// 如果存在当前ip,则验证当前ip的访问次数
		// 如果大于限制阀值,判断达到阀值的时间,如果不大于[用户访问最小安全时间]则视为恶意访问,跳转到异常页面
		if (ipMap.containsKey(ip)) {
			Long[] ipInfo = ipMap.get(ip);
			ipInfo[0] = ipInfo[0] + 1;
			if (ipInfo[0] > LIMIT_NUMBER) {
				Long ipAccessTime = ipInfo[1];
				Long currentTimeMillis = System.currentTimeMillis();
				// 限制时间内
				if (currentTimeMillis - ipAccessTime <= MIN_SAFE_TIME) {
					System.out
							.println(ip + " 在[" + (currentTimeMillis - ipAccessTime) + "]ms内,共访问了[" + ipInfo[0] + "]次");
					limitedIpMap.put(ip, currentTimeMillis + LIMITED_TIME_MILLIS);
					forward(request, response, currentTimeMillis + LIMITED_TIME_MILLIS);
					return;
				} else {
					initIpVisitsNumber(ipMap, ip);
				}
			}
		} else {
			initIpVisitsNumber(ipMap, ip);
		}
		context.setAttribute("ipMap", ipMap);
		chain.doFilter(request, response);
	}

	@Override
	public void destroy() {

	}

	/**
	 * @Description 跳转页面
	 * @author zhangyd
	 * @date 2016年8月17日 下午5:58:43
	 * @param request
	 * @param response
	 * @param remainingTime
	 *            剩余限制时间
	 * @throws ServletException
	 * @throws IOException
	 */
	private void forward(HttpServletRequest request, HttpServletResponse response, long remainingTime)
			throws ServletException, IOException {
		request.setAttribute("remainingTime", remainingTime);
		request.getRequestDispatcher("/error/overLimitIP").forward(request, response);
	}

	/**
	 * @Description 是否是被限制的IP
	 * @author zhangyd
	 * @date 2016年8月8日 下午5:39:17
	 * @param limitedIpMap
	 * @param ip
	 * @return true : 被限制 | false : 正常
	 */
	private boolean isLimitedIP(ConcurrentHashMap<String, Long> limitedIpMap, String ip) {
		if (limitedIpMap == null || limitedIpMap.isEmpty() || ip == null) {
			// 没有被限制
			return false;
		}
		return limitedIpMap.containsKey(ip);
	}

	/**
	 * 初始化用户访问次数和访问时间
	 * 
	 * @author zhangyd
	 * @date 2016年7月29日 上午10:01:39
	 * @param ipMap
	 * @param ip
	 */
	private void initIpVisitsNumber(ConcurrentHashMap<String, Long[]> ipMap, String ip) {
		Long[] ipInfo = new Long[2];
		ipInfo[0] = 0L;// 访问次数
		ipInfo[1] = System.currentTimeMillis();// 初次访问时间
		ipMap.put(ip, ipInfo);
	}

}

 加入Spring Task定时器。配置文件:applicationContext.xml

 

<context:component-scan base-package="com.test" >
	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<!-- 配置定时任务 -->
<task:annotation-driven />

 IPFilterTask

import java.util.Date;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.ServletContext;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.context.ContextLoaderListener;

/**
 * @Description IP限制过滤器定时任务,过滤受限的IP,剔除已经到期的限制IP
 * @author zhangyd
 * @date 2016年8月18日 上午9:39:19
 * @version V1.0
 * @since JDK : 1.7
 * @modify 将IP限制过滤手动触发改为定时任务
 */
@Component
public class IPFilterTask {

	/**
	 * @Description 30s执行一次过滤操作
	 * @author zhangyd
	 * @date 2016年8月17日 下午5:49:55
	 */
	@Scheduled(cron = "0/30 * *  * * ? ")
	public void filterLimitedIpMap() {
		ServletContext context = ContextLoaderListener.getCurrentWebApplicationContext().getServletContext();
		@SuppressWarnings("unchecked")
		ConcurrentHashMap<String, Long> limitedIpMap = (ConcurrentHashMap<String, Long>) context
				.getAttribute("limitedIpMap");
		if (limitedIpMap.isEmpty()) {
			return;
		}
		Iterator<Entry<String, Long>> it = limitedIpMap.entrySet().iterator();
		long currentTimeMillis = System.currentTimeMillis();
		while (it.hasNext()) {
			Entry<String, Long> e = it.next();
			long expireTimeMillis = e.getValue();
			if (expireTimeMillis <= currentTimeMillis) {
				it.remove();
				System.out.println(new Date() + "时,去掉了一个限制用户[" + e.getKey() + "]");
			}
		}
	}
}

 

    欢迎各位博友的指正,希望各位博友不吝赐教!大笑

 

0
1
分享到:
评论
2 楼 zjj350 2017-06-10  
你这版多线程好像有问题啊,context线程不安全啊。
1 楼 hellotieye 2016-08-19  
能不能不写servlet,servlet也不是同步的。


相关推荐

    手把手教你学单片机(第二版) 周兴华.pdf

    手把手教你学单片机(第二版) 周兴华.pdf

    手把手教你学单片机(第二版

    手把手教你学单片机(第二版),汇编版(清晰)。 51单片机入门的经典书籍。书中例子句句有注释。 比C语言更彻底的面向硬件,让人有直接与单片机对话的感觉。 UVZ文件,可有UnicornViewer打开。 UnicornViewer软件...

    手把手教你学DSP:基于TMS320F28335

    手把手教你学DSP:基于TMS320F28335 手把手教你学DSP:基于TMS320F28335 手把手教你学DSP:基于TMS320F28335 手把手教你学DSP:基于TMS320F28335 手把手教你学DSP:基于TMS320F28335 手把手教你学DSP:基于TMS320F...

    手把手教你如何建立自己的Linux系统 第二版.pdf

    如果一切顺利,系统会使用你自定义的内核启动。然而,可能需要手动安装一些驱动程序,因为不是所有驱动都包含在内核源码中,特别是硬件特定的驱动。这些驱动通常以第三方模块的形式提供,或者需要编译为内核的一部分...

    Android 手把手教您自定义ViewGroup(一)

    我们将通过分析标题"Android 手把手教您自定义ViewGroup(一)"以及提供的博客链接,深入探讨这个主题。 首先,自定义ViewGroup意味着你需要扩展`android.view.ViewGroup`类,这是一个容器,可以包含多个子视图...

    手把手教你学DSPPDF

    【标题】"手把手教你学DSPPDF"是一份针对数字信号处理(DSP)初学者的教程性PDF文档,旨在引领读者逐步掌握这一领域的基础知识。该文档可能包含了从理论概念到实际应用的全面讲解,适合那些希望踏入数字信号处理世界...

    《手把手教你学51单片机》教材pdf

    《手把手教你学51单片机》教材的出版,对于那些渴望踏入嵌入式开发领域的初学者而言,无疑是一份难得的珍藏资料。51单片机作为微控制器的经典代表,一直占据着嵌入式系统教学和应用的前沿,其地位不可动摇。本书以51...

    手把手教你学单片机(第二版)光盘源代码

    《手把手教你学单片机(第二版)光盘源代码》是一套全面且实用的单片机学习资源,包含了多个章节的实验程序,旨在帮助初学者和进阶者深入理解单片机的工作原理和应用。这个压缩包提供的源代码是基于Keil集成开发环境的...

    手把手教你学51单片机C语言版PDF

    《手把手教你学51单片机C语言版》是一本专为初学者设计的教程,旨在帮助读者从零开始掌握51系列单片机的编程与应用。这本书由权威的电子技术教育平台www.kingst.org提供,是学习单片机C语言编程的宝贵资源。下面将...

    手把手教你dsp28335,高清pdf

    手把手教你学DSP28335高清pdf文件,北京航空航天大学出版社

    手把手教你学28335

    手把手教你学28335PDF文档,看了这个确实和2812有了对比

    手把手教你学51单片机-资源

    《手把手教你学51单片机》是宋雪松先生编写的一本深入浅出的单片机学习教程,特别适合初学者入门。51单片机是微控制器领域非常经典的一款芯片,由Intel公司推出,因其8051内核而得名,现在由许多厂商如Atmel、...

    手把手教你学dsp2812,手把手教你学dsp2812pdf下载,C,C++

    《手把手教你学DSP2812》是一本专为初学者设计的 DSP(Digital Signal Processor)学习指南,主要围绕TI公司的TMS320F2812 DSP芯片进行讲解。这本书以其全面且易懂的特性,为读者提供了一个深入理解数字信号处理及其...

    手把手教你学DSP28335

    手把手教你学DSP28335,PDF格式,有助于随时随地可以学习知识。

    手把手教你学dsp.pdf

    标题《手把手教你学DSP.pdf》中的DSP指的是数字信号处理器(Digital Signal Processor),它是一种专门用于数字信号处理的微处理器。数字信号处理器在实时快速地完成特定数字信号处理运算方面表现突出,具有处理速度...

    手把手教你学dsp

    手把手教你学dsp F2812 顾伟刚

    手把手教你如何建立自己的Linux系统 第二版

    ### 手把手教你如何建立自己的Linux系统(LFS速成手册)第二版 #### 前言 在深入了解如何从零构建一个Linux系统之前,我们先来了解一下文章背景及其目标。该指南由孙海勇撰写,旨在帮助初学者通过一个实践性的过程...

    手把手教你如何建立自己的Linux系统 第二版(LFS速成手册).pdf

    《手把手教你如何建立自己的Linux系统 第二版(LFS速成手册)》是一本针对Linux From Scratch(LFS)项目的学习指南,旨在帮助读者深入理解Linux操作系统的工作原理,并通过亲手构建系统来提升技术水平。LFS项目允许...

    《手把手教你学dsp2812》顾卫刚.PDF

    根据给出的文件信息,《手把手教你学dsp2812》是顾卫刚所著的一本关于DSP2812的教材书籍。DSP2812指的是德州仪器(Texas Instruments,简称TI)的TMS320F2812数字信号处理器。TMS320F2812是一款32位的高性能数字信号...

    手把手教你DSP配套资料

    “手把手教你DSP配套资料”这一压缩包很可能是包含了一系列关于DSP的学习材料,可能包括教程文档、示例代码、实验指导等。通过这些资料,你可以深入理解DSP的基本原理、算法和应用,逐步掌握实际操作技能,为你的...

Global site tag (gtag.js) - Google Analytics