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

Java JSR-356带Tomcat的WebSocket-如何限制单个IP地址内的连接?,java,tomcat,servlets,websocket,jsr356,Java,Tomcat,Servlets,Websocket,Jsr356,我制作了一个JSR-356@ServerEndpoint,其中我想限制来自单个IP地址的活动连接,以防止简单的DDOS攻击 注意,我正在搜索Java解决方案(JSR-356、Tomcat或Servlet3.0规范) 我尝试过自定义端点配置器,但即使在握手请求对象中,我也无法访问IP地址 如何在没有iptables等外部软件的情况下从单个IP地址限制JSR-356连接计数?根据Tomcat developer@mark thomas client IP未通过JSR-356公开,因此不可能用纯JSR

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

注意,我正在搜索Java解决方案(JSR-356、Tomcat或Servlet3.0规范)

我尝试过自定义端点配置器,但即使在
握手请求
对象中,我也无法访问IP地址


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

根据Tomcat developer@mark thomas client IP未通过JSR-356公开,因此不可能用纯JSR-356 API-s实现此功能

您必须使用相当丑陋的黑客来绕过标准的限制

需要做的事情归结起来是:

  • 在初始请求时(在websocket握手之前),为每个用户生成一个包含其IP的令牌
  • 沿着链传递令牌,直到它到达端点实现
  • 要实现这一点,至少有两种不切实际的选择

    使用HttpSession
  • 使用
    ServletRequestListener
  • 对传入请求调用
    request.getSession()
    ,以确保其具有会话并将客户端IP存储为会话属性
  • 创建一个
    ServerEndpointConfig.Configurator
    ,它使用
    modifyHandshake
    方法将客户端IP从
    HandshakeRequest#getHttpSession
    提升,并将其作为用户属性附加到
    EndpointConfig
  • EndpointConfig
    user properties获取客户端IP,将其存储在map或其他文件中,如果每个IP的会话数超过阈值,则触发清理逻辑
  • 您还可以使用
    @WebFilter
    而不是
    ServletRequestListener

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

    将IP作为URL中的加密令牌传递
  • 创建附加到非websocket入口点的servlet或筛选器。e、 g.
    /mychat
  • 获取客户端IP,使用随机salt和密钥对其进行加密以生成令牌
  • 使用
    ServletRequest#getRequestDispatcher
    将请求转发到
    /mychat/TOKEN
  • 将端点配置为使用路径参数,例如
    @ServerEndpoint(“/mychat/{token}”)
  • @PathParam
    中取出令牌并解密以获取客户端IP。如果每个IP的会话数超过阈值,则将其存储在map或其他文件中,并触发清理逻辑
  • 为了便于安装,您可能希望在应用程序启动时生成加密密钥

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

    另见:


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


    套接字对象隐藏在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;
    }
    
    公共静态InetSocketAddress getRemoteAddress(WsSession会话){
    if(会话==null){
    返回null;
    }
    Async Async=session.getAsyncRemote();
    InetSocketAddress addr=(InetSocketAddress)getFieldInstance(异步,
    “基本#sos#SocketRapper#socket#sc#remoteAddress”);
    返回地址;
    }
    私有静态对象getFieldInstance(对象obj,字符串fieldPath){
    字符串字段[]=fieldPath.split(“#”);
    用于(字符串字段:字段){
    obj=getField(obj,obj.getClass(),field);
    if(obj==null){
    返回null;
    }
    }
    返回obj;
    }
    私有静态对象getField(对象obj、类clazz、字符串fieldName){
    对于(;clazz!=Object.class;clazz=clazz.getSuperclass()){
    试一试{
    场域;
    field=clazz.getDeclaredField(字段名);
    字段。setAccessible(true);
    返回字段.get(obj);
    }捕获(例外e){
    }            
    }
    返回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>
    
    
    javax.websocket
    javax.websocket-all
    1.1
    聚甲醛
    假如
    org.apache.tomcat
    TomcatWebSocket
    8.0.26
    假如
    
    不要为此编写代码。使用防火墙。当您开始编写Java代码时,已经太晚了。@EJP作为问题状态-需要Java解决方案。我不是在要求最好的解决方案。假设我们希望产品在许多环境中都具有最大的简单性和可移植性。从单个主机建立40000个连接非常容易,但是自动断开连接会使这变得更加困难。Tomcat在websockets上使用max connections,因此,如果它被配置为处理~200k连接,我只想通过保持连接来防止非常容易的服务阻塞,这不需要像僵尸网络这样的大量资源。你假设有一个Java解决方案,没有证据。如果你在运输产品或有其他限制,你应该在问题中说明。大多数人不是。事实上,从一台主机上建立40000个连接并不容易。很多环境都不会超过几千个。你确定你的第二个建议有效吗?也许你能提供一个要点?在Jetty 9.3.6.v20151106中尝试转发升级请求时,至少出现了500个错误。直接连接到websocket很好。为了回答我自己的意见,第二个建议根本不起作用,并且在第一个选项中不可能使用过滤器,