【问题标题】:JSR-356 WebSockets with Tomcat - How to limit connections within single IP address?JSR-356 WebSockets with Tomcat - 如何限制单个 IP 地址内的连接?
【发布时间】:2014-05-17 18:51:28
【问题描述】:

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

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

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

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

【问题讨论】:

  • 不要为此编写代码。使用防火墙。当您开始编写 Java 代码时,已经为时已晚。
  • @EJP 正如问题所述 - 需要 Java 解决方案。我不是要最佳解决方案。假设我们希望运送到许多环境的产品具有最大的简单性和可移植性。从单个主机建立 40 000 个连接非常容易,但自动断开连接会使这变得更加困难。 Tomcat 卡在 websockets 上的最大连接数,所以如果它被配置为处理 ~200k 连接,我只想通过保持连接来防止非常容易的服务阻塞,这不需要像僵尸网络这样的大量资源。
  • 您假设存在 Java 解决方案,但没有证据。如果您要运送产品或有其他限制,您应该在问题中说明它们。大多数人不是。事实上,从单个主机建立 40,000 个连接并不是一件容易的事。许多环境不会超过几千。

标签: java tomcat servlets websocket jsr356


【解决方案1】:

根据 Tomcat 开发者的说法,@mark-thomas 客户端 IP 通过 JSR-356 公开,因此不可能使用纯 JSR-356 API-s 实现这样的功能。

您必须使用相当丑陋的 hack 来解决标准的限制。

需要做的事情归结为:

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

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

使用 HttpSession

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

您也可以使用@WebFilter 代替ServletRequestListener

请注意,此选项可能会消耗大量资源,除非您的应用程序已经使用会话,例如用于身份验证。

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

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

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

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

另见:

【讨论】:

  • 您确定您的第二个建议有效吗?也许你可以提供一个要点?当我尝试在 Jetty 9.3.6.v20151106 中转发升级请求时,至少会收到 500 错误。直接连接到 websocket 就可以了。
  • 回答我自己的cmets,第二个建议根本不起作用,在第一个选项中无法使用过滤器,但使用监听器可以。请参阅stackoverflow.com/questions/24914008/… 了解更多信息。
  • 纠正我之前的评论 - 使用过滤器 does 在 Tomcat (7/8) 中有效,但在 Jetty 中无效。有一个针对规范的错误开放,以澄清使用(servlet)过滤器是否应该工作,请参阅stackoverflow.com/questions/26103939/…
【解决方案2】:

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

See here.

【讨论】:

    【解决方案3】:

    socket对象隐藏在WsSession中,所以可以使用反射来获取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 配置是

    <dependency>
      <groupId>javax.websocket</groupId>
      <artifactId>javax.websocket-all</artifactId>
      <version>1.1</version>
      <type>pom</type>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.tomcat</groupId>
      <artifactId>tomcat-websocket</artifactId>
      <version>8.0.26</version>
      <scope>provided</scope>
    </dependency>
    

    【讨论】:

    • 感谢您发布此解决方法。这适用于 Tomcat 8.0.x 版本。不幸的是,这不适用于 Tomcat 8.5.x。您是否碰巧有更新以使其与 Tomcat 8.5.x 一起使用?
    • Tomcat 8.5 似乎需要"base#socketWrapper#socket#sc#remoteAddress"(注意sos 缺失)。
    • 另外,如果套接字是 APR 的管理器(即https),这将不起作用。
    • 在 Tomcat 9.0.10 上测试,可与“base#socketWrapper#socket#sc#remoteAddress”配合使用
    【解决方案4】:

    如果使用带有Undertow Websocket引擎的Springboot,请尝试以下方式获取IP。

     @OnOpen
        public void onOpen(Session session) {
            UndertowSession us = (UndertowSession) session;
            String ip = us.getWebSocketChannel().getSourceAddress().getHostString();
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-03-18
      • 1970-01-01
      • 2015-02-14
      • 1970-01-01
      • 2013-10-13
      • 1970-01-01
      • 1970-01-01
      • 2020-04-28
      相关资源
      最近更新 更多