JSR-356 WebSockets with Tomcat - 如何限制单个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实现这样的功能。

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

需要做的归结为:

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

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

使用 HttpSession

  1. 使用ServletRequestListener侦听传入的 HTTP 请求
  2. 对传入请求调用request.getSession(),以确保它具有会话并将客户端 IP 存储为会话属性。
  3. 创建一个ServerEndpointConfig.Configurator,该从HandshakeRequest#getHttpSession中提升客户端 IP,并使用 modifyHandshake 方法将其作为用户属性附加到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(如果未加密)。

另请参阅:

  • Apache Tomcat 8 Websocket 源和客户端地址
  • 查找从给定客户端 IP 创建的活动会话数
  • 从 Web 套接字@ServerEndpoint中的 HttpServletRequest 访问 HttpSession
  • https://tyrus.java.net/documentation/1.4/index/websocket-api.html
  • http://docs.oracle.com/javaee/7/tutorial/doc/websocket010.htm#BABJAIGH

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

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

看这里。

如果使用带有

Undertow Websocket 引擎的 Springboot,请尝试以下方式获取 IP。

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

如果使用:实现 'io.quarkus:quarkus-websockets'

@OnOpen
  public void onOpen(final Session session, final @PathParam("userId") String userId) {
    UndertowSession us = (UndertowSession) session;
    System.out.println("Remote Address: " + us.getChannel().remoteAddress());
    SESSIONS.put(userId, session);
    log.info("User " + userId + " joined");
  }

相关内容

  • 没有找到相关文章

最新更新