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
JSR-356 WebSockets with Tomcat - How to limit connections within single IP address?
提问by Piotr Müller
I made a JSR-356 @ServerEndpoint
in 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 HandshakeRequest
object.
我尝试过自定义端点配置器,但即使在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:
需要做的事情归结为:
- Generate each user a token that contains their IP on initial request (before websocket handshake)
- Pass the token down the chain until it reaches endpoint implementation
- 在初始请求时(在 websocket 握手之前)为每个用户生成一个包含其 IP 的令牌
- 将令牌沿链向下传递,直到它到达端点实现
There are at least two hacky options to achieve that.
至少有两个 hacky 选项可以实现这一点。
Use HttpSession
使用 HttpSession
- Listen to incoming HTTP requests with a
ServletRequestListener
- Call
request.getSession()
on incoming request to ensure it has a session and store client IP as a session attribute. - Create a
ServerEndpointConfig.Configurator
that lifts client IP fromHandshakeRequest#getHttpSession
and attaches it toEndpointConfig
as a user property using themodifyHandshake
method. - Get the client IP from
EndpointConfig
user properties, store it in map or whatever and trigger cleanup logic if the number of sessions per IP exceeds a threshold.
- 侦听传入的 HTTP 请求
ServletRequestListener
- 调用
request.getSession()
传入请求以确保它具有会话并将客户端 IP 存储为会话属性。 - 使用该方法创建一个
ServerEndpointConfig.Configurator
从中提取客户端 IPHandshakeRequest#getHttpSession
并将其附加EndpointConfig
为用户属性的对象modifyHandshake
。 - 从
EndpointConfig
用户属性中获取客户端 IP ,将其存储在地图或其他任何内容中,如果每个 IP 的会话数超过阈值,则触发清理逻辑。
You can also use a @WebFilter
instead 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 作为加密令牌传递
- Create a servlet or a filter that attaches to a non websocket entry point. e.g.
/mychat
- Get client IP, encrypt it with a random salt and a secret key to generate a token.
- Use
ServletRequest#getRequestDispatcher
to forward the request to/mychat/TOKEN
- Configure your endpoint to use path parameters e.g.
@ServerEndpoint("/mychat/{token}")
- Lift the token from
@PathParam
and 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.
- 创建附加到非 websocket 入口点的 servlet 或过滤器。例如
/mychat
- 获取客户端 IP,使用随机盐和密钥对其进行加密以生成令牌。
- 用于
ServletRequest#getRequestDispatcher
将请求转发到/mychat/TOKEN
- 配置您的端点以使用路径参数,例如
@ServerEndpoint("/mychat/{token}")
- 提取令牌
@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.5
directly thus spoofing the client IP if it's not encrypted.
请注意,即使您正在进行客户端不可见的内部调度,您也需要对 IP 进行加密。/mychat/2.3.4.5
如果没有加密,没有什么可以阻止攻击者直接连接从而欺骗客户端 IP。
See also:
也可以看看:
- apache tomcat 8 websocket origin and client address
- Find number of active sessions created from a given client IP
- Accessing HttpSession from HttpServletRequest in a Web Socket @ServerEndpoint
- https://tyrus.java.net/documentation/1.4/index/websocket-api.html
- http://docs.oracle.com/javaee/7/tutorial/doc/websocket010.htm#BABJAIGH
回答by Atorian
回答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>