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

时间:2014-04-05 11:08:33

标签: java tomcat servlets websocket jsr356

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

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

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

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

3 个答案:

答案 0 :(得分:11)

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

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

需要做的事情归结为:

  1. 在初始请求时(在websocket握手之前)生成每个用户包含其IP的令牌
  2. 将令牌传递到链中,直到达到端点实现
  3. 实现这一目标至少有两个hacky选项。

    使用HttpSession

    1. 使用ServletRequestListener
    2. 收听传入的HTTP请求
    3. 对传入请求调用request.getSession()以确保其具有会话并将客户端IP存储为会话属性。
    4. 创建ServerEndpointConfig.Configurator,将HandshakeRequest#getHttpSession的客户端IP提升,并使用EndpointConfig方法将其作为用户属性附加到modifyHandshake
    5. EndpointConfig用户属性获取客户端IP,将其存储在地图或其他任何位置,并在每个IP的会话数超过阈值时触发清理逻辑。
    6. 您还可以使用@WebFilter代替ServletRequestListener

      请注意,除非您的应用程序已使用会话,否则此选项可能会占用大量资源。用于身份验证。

      在IP

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

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

        另见:

答案 1 :(得分:3)

套接字对象隐藏在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>

答案 2 :(得分:0)

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

See here.