引用自:https://843977358.iteye.com/blog/2318143
经过验证之后做了优化:增加添加到map时惰性删除功能,随时清除过期数据。由于项目pv小,没有经过大流量访问实测。自己压测1s500访问量没有问题。
请见代码:
/**
* ip限制过滤器
*/
public class IPLimitFilter implements Filter {
/**
* 默认限制时间1小时(单位:ms)
*/
private static final long LIMITED_TIME_MILLIS = 60 * 1000;
/**
* 用户连续访问最高阀值,超过该值则认定为恶意操作的IP,进行限制
*/
private static final int LIMIT_NUMBER = 100;
/**
* 用户访问最小安全时间(一分钟),在该时间内如果访问次数大于阀值,则记录为恶意IP,否则视为正常访问
*/
private static final int MIN_SAFE_TIME = 60*1000 ;
private FilterConfig config;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.config = filterConfig; //设置属性filterConfig
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
request.setCharacterEncoding("utf-8");
String ip = request.getRemoteAddr();
System.out.println("^^^^^^^^^^^^^^^ip = " + ip);
//忽略本机地址
if(StringUtils.equalsIgnoreCase(ip,"127.0.0.1")){
filterChain.doFilter(servletRequest, servletResponse);
return;
}
ServletContext context = config.getServletContext();
// 获取限制IP存储器:存储被限制的IP信息
ConcurrentHashMap<String, Long> limitedIpMap = (ConcurrentHashMap<String, Long>) context.getAttribute("limitedIpMap");
// 过滤受限的IP
filterLimitedIpMap(limitedIpMap);
// 判断是否是被限制的IP,如果是则跳到异常页面
if (isLimitedIP(limitedIpMap, ip)) {
response.setCharacterEncoding("utf-8");
long limitedTime = limitedIpMap.get(ip) - System.currentTimeMillis();
System.out.println("XXXXXXXXXXXXXXXXX限制ip = " + ip);
response.getWriter().write("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html\"; " +
"charset=\"utf-8\"></head><body><center style='padding-top:100px'><span style='color:red'>访问频繁," +
"请"+((limitedTime / 1000) + (limitedTime % 1000 > 0 ? 1 : 0))+"秒之后再试 " +
"!</span></center></body></html>");
return;
}
// 获取IP存储器
ConcurrentHashMap<String, Long[]> ipMap = (ConcurrentHashMap<String, Long[]>) context.getAttribute("ipMap");
// 判断存储器中是否存在当前IP,如果没有则为初次访问,初始化该ip
// 如果存在当前ip,则验证当前ip的访问次数
// 如果大于限制阀值,判断达到阀值的时间,如果不大于[用户访问最小安全时间]则视为恶意访问,跳转到异常页面
if (ipMap != null && ipMap.size() > 0 && ipMap.containsKey(ip)) {
Long[] ipInfo = ipMap.get(ip);
if(ipInfo!=null){
ipInfo[0] = ipInfo[0] + 1;
System.out.println("当前第[" + (ipInfo[0]) + "]次访问");
if (ipInfo[0] > LIMIT_NUMBER) {
Long ipAccessTime = ipInfo[1];
Long currentTimeMillis = System.currentTimeMillis();
if (currentTimeMillis - ipAccessTime <= MIN_SAFE_TIME) {
if(limitedIpMap==null){
limitedIpMap = new ConcurrentHashMap<>();
}
limitedIpMap.put(ip, currentTimeMillis + LIMITED_TIME_MILLIS);
ipMap.remove(ip);
context.setAttribute("limitedIpMap", limitedIpMap);
response.setCharacterEncoding("utf-8");
response.getWriter().write("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html\"; " +
"charset=\"utf-8\"></head><body><center style='padding-top:100px'><span " +
"style='color:red'>访问频繁,请1小时之后再试 ! </span></center></body></html>");
return;
} else {
ipMap = initIpVisitsNumber(ipMap, ip);
}
}else {
ipMap.put(ip, ipInfo);
}
}
} else {
ipMap = initIpVisitsNumber(ipMap, ip);
}
context.setAttribute("ipMap", ipMap);
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
/**
* @param limitedIpMap
* @Description 过滤受限的IP,剔除已经到期的限制IP
*/
private void filterLimitedIpMap(ConcurrentHashMap<String, Long> limitedIpMap) {
if (limitedIpMap == null) {
return;
}
Set<String> keys = limitedIpMap.keySet();
Iterator<String> keyIt = keys.iterator();
long currentTimeMillis = System.currentTimeMillis();
while (keyIt.hasNext()) {
long expireTimeMillis = limitedIpMap.get(keyIt.next());
if (expireTimeMillis <= currentTimeMillis) {
keyIt.remove();
}
}
}
/**
* @param limitedIpMap
* @param ip
* @return true : 被限制 | false : 正常
* @Description 是否是被限制的IP
*/
private boolean isLimitedIP(ConcurrentHashMap<String, Long> limitedIpMap, String ip) {
if (limitedIpMap == null || ip == null) {
// 没有被限制
return false;
}
Set<String> keys = limitedIpMap.keySet();
Iterator<String> keyIt = keys.iterator();
while (keyIt.hasNext()) {
String key = keyIt.next();
if (key.equals(ip)) {
// 被限制的IP
return true;
}
}
return false;
}
/**
* 初始化用户访问次数和访问时间
*
* @param ipMap
* @param ip
*/
private ConcurrentHashMap<String, Long[]> initIpVisitsNumber(ConcurrentHashMap<String, Long[]> ipMap, String ip) {
Long[] ipInfo = new Long[2];
ipInfo[0] = 1L;// 访问次数
ipInfo[1] = System.currentTimeMillis();// 初次访问时间
if (ipMap == null) {
ipMap = new ConcurrentHashMap<>();
}
//惰性删除,每次增加新的ip时删除过期ip
Iterator<Map.Entry<String, Long[]>> iterator = ipMap.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<String, Long[]> next = iterator.next();
if(System.currentTimeMillis() - next.getValue()[1] > MIN_SAFE_TIME ){
System.out.println("删除过期的ip:"+next.getKey());
iterator.remove();
}
}
ipMap.put(ip, ipInfo);
return ipMap;
}
}