带有Tomcat的JSR-356 WebSockets – 如何限制单个IP地址内的连接?
我创建了一个JSR-356 @ServerEndpoint
,我想在其中限制来自单个IP地址的活动连接,以防止简单的DDOS攻击。
请注意,我正在搜索Java解决方案(JSR-356,Tomcat或Servlet 3.0规范)。
我尝试过自定义端点配置程序,但即使在HandshakeRequest
对象中我也无法访问IP地址。
如何在没有像iptables这样的外部软件的情况下限制单个IP地址的JSR-356连接数?
根据Tomcat的开发人员@mark-thomas客户端IP 没有通过JSR-356公开,因此用纯JSR-356 API-s实现这样的function是不可能的。
你必须使用一个相当丑陋的黑客来解决标准的限制。
需要做的事情归结为:
- 在初始请求时(在websocket握手之前)为每个用户生成包含其IP的令牌
- 将令牌传递到链中,直到它到达端点实现
至少有两个hacky选项可以实现这一目标。
使用HttpSession
- 使用
ServletRequestListener
监听传入的HTTP请求 - 在传入请求上调用
request.getSession()
以确保它具有会话并将客户端IP存储为会话属性。 - 创建一个
ServerEndpointConfig.Configurator
,它从HandshakeRequest#getHttpSession
客户端IP,并使用modifyHandshake
方法将其作为用户属性附加到EndpointConfig
。 - 从
EndpointConfig
用户属性获取客户端IP,将其存储在映射或其他任何位置,并在每个IP的会话数超过阈值时触发清理逻辑。
您也可以使用@WebFilter
而不是ServletRequestListener
请注意,除非您的应用程序已使用会话(例如用于身份validation),否则此选项的资源消耗很高。
将IP作为URL中的加密令牌传递
- 创建一个附加到非websocket入口点的servlet或filter。 例如
/mychat
- 获取客户端IP,使用随机盐和密钥对其进行加密以生成令牌。
- 使用
ServletRequest#getRequestDispatcher
将请求转发到/mychat/TOKEN
- 配置端点以使用路径参数,例如
@ServerEndpoint("/mychat/{token}")
- 从
@PathParam
令牌并解密以获取客户端IP。 将其存储在地图或其他任何位置,并在每个IP的会话数超过阈值时触发清理逻辑。
为了便于安装,您可能希望在应用程序启动时生成加密密钥。
请注意,即使您正在进行客户端不可见的内部调度,您也需要加密IP。 没有什么可以阻止攻击者直接连接到/mychat/2.3.4.5
,从而欺骗客户端IP(如果它没有加密)。
也可以看看:
- apache tomcat 8 websocket起源和客户端地址
- 查找从给定客户端IP创建的活动会话数
- 从Web Socket @ServerEndpoint中的HttpServletRequest访问HttpSession
- https://tyrus.java.net/documentation/1.4/index/websocket-api.html
- http://docs.oracle.com/javaee/7/tutorial/doc/websocket010.htm#BABJAIGH
套接字对象隐藏在WsSession中,因此您可以使用reflection来获取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; }
而pom配置是
javax.websocket javax.websocket-all 1.1 pom provided org.apache.tomcat tomcat-websocket 8.0.26 provided
如果您使用符合JSR-356的Tyrus,那么您可以从Session实例获取IP地址,但这是一种非标准方法。
看这里。