7

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

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

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

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

4

4 回答 4

12

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

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

需要做的事情归结为:

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

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

使用 HttpSession

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

您也可以使用 a@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。

也可以看看:

于 2014-04-12T01:37:34.127 回答
4

套接字对象隐藏在 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>
于 2015-09-17T11:47:09.327 回答
0

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

看这里。

于 2014-08-04T13:14:02.047 回答
0

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

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

于 2021-08-10T03:24:36.927 回答