带有Tomcat的JSR-356 WebSockets – 如何限制单个IP地址内的连接?

我创建了一个JSR-356 @ServerEndpoint ,我想在其中限制来自单个IP地址的活动连接,以防止简单的DDOS攻击。

请注意,我正在搜索Java解决方案(JSR-356,Tomcat或Servlet 3.0规范)。

我尝试过自定义端点配置程序,但即使在HandshakeRequest对象中我也无法访问IP地址。

如何在没有像iptables这样的外部软件的情况下限制单个IP地址的JSR-356连接数?

根据Tomcat的开发人员@mark-thomas客户端IP 没有通过JSR-356公开,因此用纯JSR-356 API-s实现这样的function是不可能的。

你必须使用一个相当丑陋的黑客来解决标准的限制。

需要做的事情归结为:

  1. 在初始请求时(在websocket握手之前)为每个用户生成包含其IP的令牌
  2. 将令牌传递到链中,直到它到达端点实现

至少有两个hacky选项可以实现这一目标。

使用HttpSession

  1. 使用ServletRequestListener监听传入的HTTP请求
  2. 在传入请求上调用request.getSession()以确保它具有会话并将客户端IP存储为会话属性。
  3. 创建一个ServerEndpointConfig.Configurator ,它从HandshakeRequest#getHttpSession客户端IP,并使用modifyHandshake方法将其作为用户属性附加到EndpointConfig
  4. EndpointConfig用户属性获取客户端IP,将其存储在映射或其他任何位置,并在每个IP的会话数超过阈值时触发清理逻辑。

您也可以使用@WebFilter而不是ServletRequestListener

请注意,除非您的应用程序已使用会话(例如用于身份validation),否则此选项的资源消耗很高。

将IP作为URL中的加密令牌传递

  1. 创建一个附加到非websocket入口点的servlet或filter。 例如/mychat
  2. 获取客户端IP,使用随机盐和密钥对其进行加密以生成令牌。
  3. 使用ServletRequest#getRequestDispatcher将请求转发到/mychat/TOKEN
  4. 配置端点以使用路径参数,例如@ServerEndpoint("/mychat/{token}")
  5. @PathParam令牌并解密以获取客户端IP。 将其存储在地图或其他任何位置,并在每个IP的会话数超过阈值时触发清理逻辑。

为了便于安装,您可能希望在应用程序启动时生成加密密钥。

请注意,即使您正在进行客户端不可见的内部调度,您也需要加密IP。 没有什么可以阻止攻击者直接连接到/mychat/2.3.4.5 ,从而欺骗客户端IP(如果它没有加密)。

也可以看看:

套接字对象隐藏在WsSession中,因此您可以使用reflection来获取IP地址。 该方法的执行时间约为1ms。 这个解决方案并不完美但有用。

 public static InetSocketAddress getRemoteAddress(WsSession session) { if(session == null){ return null; } Async async = session.getAsyncRemote(); InetSocketAddress addr = (InetSocketAddress) getFieldInstance(async, "base#sos#socketWrapper#socket#sc#remoteAddress"); return addr; } private static Object getFieldInstance(Object obj, String fieldPath) { String fields[] = fieldPath.split("#"); for(String field : fields) { obj = getField(obj, obj.getClass(), field); if(obj == null) { return null; } } return obj; } private static Object getField(Object obj, Class clazz, String fieldName) { for(;clazz != Object.class; clazz = clazz.getSuperclass()) { try { Field field; field = clazz.getDeclaredField(fieldName); field.setAccessible(true); return field.get(obj); } catch (Exception e) { } } return null; } 

而pom配置是

  javax.websocket javax.websocket-all 1.1 pom provided   org.apache.tomcat tomcat-websocket 8.0.26 provided  

如果您使用符合JSR-356的Tyrus,那么您可以从Session实例获取IP地址,但这是一种非标准方法。

看这里。