第一版(點擊這兒)寫的有點沖沖忙忙,也沒有考慮到線程安全方面的問題,在這兒先感謝博友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() + "]");
}
}
}
}
歡迎各位博友的指正,希望各位博友不吝賜教!