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

4uqofj5v  于 2022-11-13  发布在  其他
关注(0)|答案(5)|浏览(146)

我创建了一个JSR-356 @ServerEndpoint,在其中我希望限制来自单个IP地址的活动连接,以防止简单的DDOS攻击。
请注意,我正在搜索Java解决方案(JSR-356、Tomcat或Servlet 3.0规范)。
我尝试过自定义端点配置器,但即使在HandshakeRequest对象中也无法访问IP地址。

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

vltsax25

vltsax251#

根据Tomcat开发人员@mark-thomas的说法,客户端IP通过JSR-356暴露,因此不可能使用纯JSR-356 API实现这样的功能。
你必须使用一个相当丑陋的黑客来解决标准的限制。
需要做的事情可以归结为:
1.在初始请求时(在WebSocket握手之前)为每个用户生成一个包含其IP的令牌
1.将令牌沿链向下传递,直到它到达端点实现
至少有两种方法可以实现这一点。

使用HttpSession

1.使用ServletRequestListener侦听传入的HTTP请求
1.在传入请求上调用request.getSession()以确保它具有会话并将客户端IP存储为会话属性。
1.使用modifyHandshake方法创建一个ServerEndpointConfig.Configurator,该ServerEndpointConfig.ConfiguratorHandshakeRequest#getHttpSession提取客户端IP,并将其作为用户属性附加到EndpointConfig
1.从EndpointConfig用户属性中获取客户端IP,将其存储在map或其他文件中,如果每个IP的会话数超过阈值,则触发清理逻辑。
也可以使用@WebFilter代替ServletRequestListener
请注意,除非您的应用程序已经使用会话(例如,用于身份验证),否则此选项可能会占用大量资源。

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

1.创建附加到非WebSocket入口点的servlet或过滤器。例如/mychat
1.获取客户端IP,使用随机盐和密钥对其进行加密以生成令牌。
1.使用ServletRequest#getRequestDispatcher将请求转发到/mychat/TOKEN
1.将终结点配置为使用路径参数,例如@ServerEndpoint("/mychat/{token}")
1.从@PathParam中提取令牌并解密以获得客户端IP。将其存储在map或其他文件中,如果每个IP的会话数超过阈值,则触发清理逻辑。
为了便于安装,您可能希望在应用程序启动时生成加密密钥。
请注意,即使您正在执行客户端不可见的内部调度,您也需要加密IP。如果客户端IP未加密,没有任何方法可以阻止攻击者直接连接到/mychat/2.3.4.5,从而欺骗客户端IP。
另请参阅:

rggaifut

rggaifut2#

套接字对象被隐藏在WsSession中,所以你可以使用反射来获取IP地址。2这个方法的执行时间大约是1ms。3这个解决方案并不完美,但是很有用。

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>
vvppvyoh

vvppvyoh3#

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

ni65a41a

ni65a41a4#

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

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

t30tvxxf5#

如果使用:实作'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");
  }

相关问题