同一个IP 访问网站, 不能同时超过配置的最大值, 可以用来保护REST IP 或者DOS攻击
/*
* Pprun's Public Domain.
*/
package mon.security;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.scheduling.annotation.Scheduled;
/**
* A filter to throttle only a limited number of requests from the same ip per second.
*
* Two parameters need to inject:
*
*
common.throttle.maxConcurrentRequests=10*
common.throttle.period=1000*
*
* If you use {@literal Spring} for {@lit DI}, it can be done as below in application {@literal web.xml}:
*
* {@code
*
* A filter to throttle only a limited number of requests from the same ip per second.
* throttleFilter
* org.springframework.web.filter.DelegatingFilterProxy
*
* }
*
* And a bean with {@code id="throttleFilter"} should be defined in {@literal application.xml}.
*
* @author pizhigang
*/
//@Component
public class ThrottleFilter implements Filter, ThrottleFilterMXBean {
private static Logger log = LoggerFactory.getLogger(ThrottleFilter.class);
// map(ip, requestCount)
private Map ip2countCache = new HashMap();
private Set blackList = new HashSet();
private int maxConcurrentRequests;
private static final long PERIOD = 1L; // second
private boolean enable = false;
@Override
public void init(FilterConfig config) throws ServletException {
// nothing
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain nextFilter) throws ServletException, IOException {
if (enable) {
final String ip = request.getRemoteAddr();
boolean isOverflow;
synchronized (this) {
Integer count = ip2countCache.get(ip);
if (count == null || count.intValue() == 0) {
count = 0;
}
if (count < maxConcurrentRequests) {
isOverflow = false;
ip2countCache.put(ip, count + 1);
} else {
isOverflow = true;
blackList.add(ip);
}
}
if (isOverflow) {
// block it
log.info(" ip {} has reached the threshold {} in {} second, block it!", new Object[]{ip, maxConcurrentRequests, PERIOD});
if (response instanceof HttpServletResponse) {
((HttpServletResponse) response).sendError(503, ip + " has too many concurrent requests per " + PERIOD + " second");
}
return;
}
} // else go ahead below
// every thing is okay, go ahead
nextFilter.doFilter(request, response);
}
// every 1 second
@Scheduled(fixedRate = PERIOD * 1000)
public void throttlingJob() {
if (enable) {
log.debug("Throttle Filter clean up job is running");
synchronized (ThrottleFilter.this) {
for (Map.Entry ip2count : ip2countCache.entrySet()) {
Integer count = ip2count.getValue();
String ip = ip2count.getKey();
if (count == null || count <= 1) {
ip2countCache.remove(ip);
} else {
if (count == maxConcurrentRequests) {
// remove from blacklist
log.info("Throttle Filter: removing {} from black list", ip);
blackList.remove(ip);
}
ip2countCache.put(ip, count - 1);
}
}
}
log.debug("Throttle Filter clean up job is done");
}
}
/**
* Any cleanup for the filter.
*/
@Override
public void destroy() {
log.warn("destorying Throttle Filter");
}
/**
* Sets the maximum number of concurrent requests for a single IP.
*/
@Required
public void setMaxConcurrentRequests(int max) {
maxConcurrentRequests = max;
}
@ManagedAttribute(description = "is the throttle filter enable or not")
@Override
public boolean isEnable() {
return enable;
}
@Required
@Override
public void setEnable(boolean enable) {
log.info("set enable to {}", enable);
if (enable == false) {
log.info("Throttle filter will be disabled");
}
this.enable = enable;
}
public Set getBlackList() {
// for exactly, it might need synchronized, but no hurt for snapshoot in one or severl seconds
return Collections.unmodifiableSet(blackList);
}
}