我创建了一个JSR-356 @ServerEndpoint,在其中我希望限制来自单个IP地址的活动连接,以防止简单的DDOS攻击。请注意,我正在搜索Java解决方案(JSR-356、Tomcat或Servlet 3.0规范)。我尝试过自定义端点配置器,但即使在HandshakeRequest对象中也无法访问IP地址。
@ServerEndpoint
HandshakeRequest
如何在没有外部软件(如iptables)的情况下限制单个IP地址的JSR-356连接数?
vltsax251#
根据Tomcat开发人员@mark-thomas的说法,客户端IP不通过JSR-356暴露,因此不可能使用纯JSR-356 API实现这样的功能。你必须使用一个相当丑陋的黑客来解决标准的限制。需要做的事情可以归结为:1.在初始请求时(在WebSocket握手之前)为每个用户生成一个包含其IP的令牌1.将令牌沿链向下传递,直到它到达端点实现至少有两种方法可以实现这一点。
1.使用ServletRequestListener侦听传入的HTTP请求1.在传入请求上调用request.getSession()以确保它具有会话并将客户端IP存储为会话属性。1.使用modifyHandshake方法创建一个ServerEndpointConfig.Configurator,该ServerEndpointConfig.Configurator从HandshakeRequest#getHttpSession提取客户端IP,并将其作为用户属性附加到EndpointConfig。1.从EndpointConfig用户属性中获取客户端IP,将其存储在map或其他文件中,如果每个IP的会话数超过阈值,则触发清理逻辑。也可以使用@WebFilter代替ServletRequestListener请注意,除非您的应用程序已经使用会话(例如,用于身份验证),否则此选项可能会占用大量资源。
ServletRequestListener
request.getSession()
modifyHandshake
ServerEndpointConfig.Configurator
HandshakeRequest#getHttpSession
EndpointConfig
@WebFilter
1.创建附加到非WebSocket入口点的servlet或过滤器。例如/mychat1.获取客户端IP,使用随机盐和密钥对其进行加密以生成令牌。1.使用ServletRequest#getRequestDispatcher将请求转发到/mychat/TOKEN1.将终结点配置为使用路径参数,例如@ServerEndpoint("/mychat/{token}")1.从@PathParam中提取令牌并解密以获得客户端IP。将其存储在map或其他文件中,如果每个IP的会话数超过阈值,则触发清理逻辑。为了便于安装,您可能希望在应用程序启动时生成加密密钥。请注意,即使您正在执行客户端不可见的内部调度,您也需要加密IP。如果客户端IP未加密,没有任何方法可以阻止攻击者直接连接到/mychat/2.3.4.5,从而欺骗客户端IP。另请参阅:
/mychat
ServletRequest#getRequestDispatcher
/mychat/TOKEN
@ServerEndpoint("/mychat/{token}")
@PathParam
/mychat/2.3.4.5
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>
vvppvyoh3#
如果您使用的是符合JSR-356的Tyrus,则可以从Session示例获取IP地址,但这是一种非标准方法。See here.
ni65a41a4#
如果使用带有Undertow WebSocket引擎的Springboot,请尝试以下方式获取IP。
@OnOpen public void onOpen(Session session) { UndertowSession us = (UndertowSession) session; String ip = us.getWebSocketChannel().getSourceAddress().getHostString();
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"); }
5条答案
按热度按时间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.Configurator
从HandshakeRequest#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。另请参阅:
rggaifut2#
套接字对象被隐藏在WsSession中,所以你可以使用反射来获取IP地址。2这个方法的执行时间大约是1ms。3这个解决方案并不完美,但是很有用。
并且POM配置是
vvppvyoh3#
如果您使用的是符合JSR-356的Tyrus,则可以从Session示例获取IP地址,但这是一种非标准方法。
See here.
ni65a41a4#
如果使用带有Undertow WebSocket引擎的Springboot,请尝试以下方式获取IP。
t30tvxxf5#
如果使用:实作'io.quarkus:quarkus-websockets'