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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22880055/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 18:38:18  来源:igfitidea点击:

JSR-356 WebSockets with Tomcat - How to limit connections within single IP address?

javatomcatservletswebsocketjsr356

提问by Piotr Müller

I made a JSR-356 @ServerEndpointin which I want to limit alive connections from single IP address, to prevent simple DDOS attacks.

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

Note that I'm search for Java solution (JSR-356, Tomcat or Servlet 3.0 specs).

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

I have tried custom endpoint configurer but I don't have access to IP address even in HandshakeRequestobject.

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

How to limit JSR-356 connection count from single IP address without external software like iptables?

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

采纳答案by anttix

According to Tomcat developer @mark-thomas client IP is notexposed via JSR-356 thus it is impossible to implement such a function with pure JSR-356 API-s.

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

You have to use a rather ugly hack to work around the limitation of the standard.

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

What needs to be done boils down to:

需要做的事情归结为:

  1. Generate each user a token that contains their IP on initial request (before websocket handshake)
  2. Pass the token down the chain until it reaches endpoint implementation
  1. 在初始请求时(在 websocket 握手之前)为每个用户生成一个包含其 IP 的令牌
  2. 将令牌沿链向下传递,直到它到达端点实现

There are at least two hacky options to achieve that.

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

Use HttpSession

使用 HttpSession

  1. Listen to incoming HTTP requests with a ServletRequestListener
  2. Call request.getSession()on incoming request to ensure it has a session and store client IP as a session attribute.
  3. Create a ServerEndpointConfig.Configuratorthat lifts client IP from HandshakeRequest#getHttpSessionand attaches it to EndpointConfigas a user property using the modifyHandshakemethod.
  4. Get the client IP from EndpointConfiguser properties, store it in map or whatever and trigger cleanup logic if the number of sessions per IP exceeds a threshold.
  1. 侦听传入的 HTTP 请求 ServletRequestListener
  2. 调用request.getSession()传入请求以确保它具有会话并将客户端 IP 存储为会话属性。
  3. 使用该方法创建一个ServerEndpointConfig.Configurator从中提取客户端 IPHandshakeRequest#getHttpSession并将其附加EndpointConfig为用户属性的对象modifyHandshake
  4. EndpointConfig用户属性中获取客户端 IP ,将其存储在地图或其他任何内容中,如果每个 IP 的会话数超过阈值,则触发清理逻辑。

You can also use a @WebFilterinstead of ServletRequestListener

您也可以使用@WebFilter代替ServletRequestListener

Note that this option can have a high resource consumption unless your application already uses sessions e.g. for authentication purposes.

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

Pass IP as an encrypted token in the URL

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

  1. Create a servlet or a filter that attaches to a non websocket entry point. e.g. /mychat
  2. Get client IP, encrypt it with a random salt and a secret key to generate a token.
  3. Use ServletRequest#getRequestDispatcherto forward the request to /mychat/TOKEN
  4. Configure your endpoint to use path parameters e.g. @ServerEndpoint("/mychat/{token}")
  5. Lift the token from @PathParamand decrypt to get client IP. Store it in map or whatever and trigger cleanup logic if the number of sessions per IP exceeds a threshold.
  1. 创建附加到非 websocket 入口点的 servlet 或过滤器。例如/mychat
  2. 获取客户端 IP,使用随机盐和密钥对其进行加密以生成令牌。
  3. 用于ServletRequest#getRequestDispatcher将请求转发到/mychat/TOKEN
  4. 配置您的端点以使用路径参数,例如 @ServerEndpoint("/mychat/{token}")
  5. 提取令牌@PathParam并解密以获取客户端 IP。如果每个 IP 的会话数超过阈值,则将其存储在地图或其他任何内容中并触发清理逻辑。

For ease of installation you may wish to generate encryption keys on application startup.

为了便于安装,您可能希望在应用程序启动时生成加密密钥。

Please note that you need to encrypt the IP even if you are doing an internal dispatch that is not visible to the client. There is nothing that would stop an attacker from connecting to /mychat/2.3.4.5directly thus spoofing the client IP if it's not encrypted.

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

See also:

也可以看看:

回答by Atorian

If you are using Tyrus which is JSR-356 compliant, then you can get the IP address from the Session instance, but this is a non-standard method.

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

See here.

看这里。

回答by eric rao

the socket object is hidden in WsSession, so you can use reflection to got the ip address. the execution time of this method is about 1ms. this solution is not prefect but useful.

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

and the pom config is

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