一、场景
针对开放在公网上的网站可能会被攻击,必须一定的安全措施,需要对并发进行限制,如限制网站的并发量或是单个IP短时间的登录次数、token认证等。
二、代码实现
以下是通过监控、记录网站的request请求数量,实现并发限制。
1.在配置文件中配置最大同时请求数
#####最大同时请求数
visitor.order.online.count=100
2.首先实现ServletRequestListener接口,在Request创建和销毁时进行记数,并将数量记录在ServletContext全局对象中。
@Slf4j
public class RequestListener implements ServletRequestListener {
private static Logger logger = LoggerFactory.getLogger(RequestListener.class);
public void requestInitialized(ServletRequestEvent arg0) {
// TODO Auto-generated method stub
synchronized (logger) {
if(logger.isInfoEnabled()){
logger.info(RunTimeLogUtil.toLog(LogObjectEnum.SERVICE, "RequestListener_requestInitialized","start", null, null));
}
//获得当前在线人数,并将其加一
Integer onlineNum = (Integer) arg0.getServletContext().getAttribute("onlineNum");
arg0.getServletContext().setAttribute("onlineNum", ++onlineNum);
if (logger.isInfoEnabled()) {
logger.info(RunTimeLogUtil.toLog(LogObjectEnum.SERVICE, "RequestListener_requestInitialized", "RequestListener_end", null, null, "onlineNum"), onlineNum);
}
}
}
public void requestDestroyed(ServletRequestEvent arg0) {
// TODO Auto-generated method stub
synchronized (logger) {
if(logger.isInfoEnabled()){
logger.info(RunTimeLogUtil.toLog(LogObjectEnum.SERVICE, "RequestListener_requestDestroyed","start", null, null));
}
//销毁请求的时候,需要将在线人数减一
Integer onlineNum = (Integer) arg0.getServletContext().getAttribute("onlineNum");
arg0.getServletContext().setAttribute("onlineNum", --onlineNum);
if(logger.isInfoEnabled()) {
logger.info(RunTimeLogUtil.toLog(LogObjectEnum.SERVICE, "RequestListener_requestDestroyed","RequestListener_end", null, null, "onlineNum"), onlineNum);
}
}
}
}
3.创建拦截器,拦截过滤所有请求,check当前请求数量是否达到配置的上限,若没有放行,否则拦截并返回错误信息。
注意加锁
public class OnLineUserInterceptor extends HandlerInterceptorAdapter{
private static Logger logger = LoggerFactory.getLogger(OnLineUserInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if(logger.isInfoEnabled()){
logger.info(RunTimeLogUtil.toLog(LogObjectEnum.SERVICE, "OnLineUserInterceptor","start", null, null));
}
synchronized (logger) {
//从缓存中获取当前连接数
Integer onlineNum = (Integer) request.getServletContext().getAttribute("onlineNum");
// 现获取请求数量
String onlineMaxCount = PropertiesReader.getWebProperty(RvsConstant.VISITOR_ORDER_ONLINE_COUNT);
// 如果未配置当前请求数量最大值,则放行
if (VisitorStringUtils.isEmpty(onlineMaxCount)) {
return super.preHandle(request, response, handler);
}
if (onlineNum > Integer.parseInt(onlineMaxCount)) {
// 如果获取当前请求数量大于最大值,不允许使用
ActionResult actionResult = new ActionResult(
VisitorStringUtils.toHexString(ErrorCodeConstant.RESOURCE_NUM_EXCEED_LIMIT),
MsgKeyConstant.RESOURCE_NUM_EXCEED_LIMIT);
logger.error(Log.toLog(StringOrNumericUtils.toHexString(ErrorCodeConstant.OPERATE_FAIL),
"Please try later again ! "));
response.getWriter().write(JsonUtils.object2Json(actionResult));
return false;
}
}
if(logger.isInfoEnabled()){
logger.info(RunTimeLogUtil.toLog(LogObjectEnum.SERVICE, "OnLineUserInterceptor","end", null, null));
}
return super.preHandle(request, response, handler);
}
}
4.在springmvc中配置该拦截器
<!-- 拦截器 配置多个将会顺序执行 -->
<mvc:interceptors>
<!-- 当前request数量校验 -->
<bean class="com.controller.common.filter.OnLineUserInterceptor"/>
</mvc:interceptors>