簡體   English   中英

JSR-356 WebSockets with Tomcat - 如何限制單個 IP 地址內的連接?

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

我制作了一個 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 實現這樣的功能。

您必須使用相當丑陋的 hack 來解決標准的限制。

需要做的事情歸結為:

  1. 根據初始請求(在 websocket 握手之前)為每個用戶生成一個包含其 IP 的令牌
  2. 將令牌沿鏈向下傳遞,直到它到達端點實現

至少有兩個 hacky 選項可以實現這一目標。

使用 HttpSession

  1. 使用ServletRequestListener監聽傳入的 HTTP 請求
  2. 對傳入請求調用request.getSession()以確保它具有會話並將客戶端 IP 存儲為會話屬性。
  3. 創建一個ServerEndpointConfig.Configurator ,它從HandshakeRequest#getHttpSession中提取客戶端 IP,並使用modifyHandshake方法將其作為用戶屬性附加到EndpointConfig
  4. EndpointConfig用戶屬性中獲取客戶端 IP,將其存儲在地圖或其他任何內容中,如果每個 IP 的會話數超過閾值,則觸發清理邏輯。

您還可以使用@WebFilter代替ServletRequestListener

請注意,此選項可能會消耗大量資源,除非您的應用程序已經使用會話(例如用於身份驗證)。

在 URL 中將 IP 作為加密令牌傳遞

  1. 創建附加到非 websocket 入口點的 servlet 或過濾器。 例如/mychat
  2. 獲取客戶端 IP,使用隨機鹽和密鑰對其進行加密以生成令牌。
  3. 使用ServletRequest#getRequestDispatcher將請求轉發到/mychat/TOKEN
  4. 配置您的端點以使用路徑參數,例如@ServerEndpoint("/mychat/{token}")
  5. @PathParam令牌並解密以獲取客戶端IP。 如果每個 IP 的會話數超過閾值,則將其存儲在地圖或其他任何內容中並觸發清理邏輯。

為了便於安裝,您可能希望在應用程序啟動時生成加密密鑰。

請注意,即使您正在執行對客戶端不可見的內部調度,您也需要對 IP 進行加密。 沒有什么可以阻止攻擊者直接連接到/mychat/2.3.4.5從而在未加密的情況下欺騙客戶端 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;
}

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>

如果您使用的是符合 JSR-356 的 Tyrus,那么您可以從 Session 實例中獲取 IP 地址,但這是一種非標准方法。

看這里。

如果使用帶有 Undertow Websocket 引擎的 Springboot,請嘗試以下方式獲取 IP。

 @OnOpen
    public void onOpen(Session session) {
        UndertowSession us = (UndertowSession) session;
        String ip = us.getWebSocketChannel().getSourceAddress().getHostString();

如果使用:實現 '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"); }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM