在 Websocket 通过 Nginx 转发用户 IP

Posted

技术标签:

【中文标题】在 Websocket 通过 Nginx 转发用户 IP【英文标题】:Forwarding User IP through Nginx at Websocket 【发布时间】:2018-11-07 08:45:36 【问题描述】:

我使用this Websocket library 和here 中的示例来创建基于websockets 的多个服务。

接收请求的主服务器是 nginx 服务器,它根据请求的来源(基于域)转发请求,因此我可以确定收到的请求是针对 websockets 的。

问题就在这里:

public void onOpen(WebSocket ws, ClientHandshake handshake) 
    System.out.println("-------------------------------");
    System.out.println(ws.getRemoteSocketAddress());
    System.out.println("-------------------------------");

getRemoteSocketAddress 将始终返回 nginx 服务器 IP,例如 /192.168.1.100:43556

我的nginx配置是:

server
    listen 80;
    server_name we.mydomain.com;
    access_log /var/log/nginx/access-ws-debug.log;
    error_log /var/log/nginx/error-ws-debug.log;

    set_real_ip_from  192.168.1.0/24;
    real_ip_header X-Real-IP;
    real_ip_recursive on;

    location / 
       proxy_pass http://192.168.1.101:54321;

       proxy_http_version 1.1;
       proxy_set_header X-Real-IP $remote_addr;
       proxy_set_header Host $http_host; #host
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       proxy_set_header Y-Real-IP $realip_remote_addr;

       proxy_set_header Upgrade $http_upgrade;
       proxy_set_header Connection "Upgrade";

       proxy_connect_timeout 1d;
       proxy_send_timeout 1d;
       proxy_read_timeout 1d;
    

nxing 配置有问题吗?或者有没有其他方法可以获取websocket后面用户的IP?


感谢马克的回答解决了。以下代码与提到的库无关。它是 jsp 中的“经典”Web 实现,没有任何库。

我将握手修改为如下所示:

@Override
public void modifyHandshake(
        ServerEndpointConfig config,
        HandshakeRequest request,
        HandshakeResponse response
) 
    HttpSession httpSession = (HttpSession) request.getHttpSession();
    config.getUserProperties().put(HttpSession.class.getName(), httpSession);
    config.getUserProperties().put("Headers",request.getHeaders());

要获取标头,只需在 OnOpen 中调用它:

@OnOpen
public void onOpen(final Session session, EndpointConfig config) //This happens when javascript opens socket session
    Object list = config.getUserProperties().get("Headers");
    System.out.println("-------------------------------------");
    System.out.println(list);
    System.out.println("-------------------------------------");

标题的返回格式如下(使用我的手机):

upgrade=[websocket], 
accept-language=[en-US,en;q=0.9,es;q=0.8], 
cookie=[JSESSIONID=0BAE99DFDE5DD81D20CFF04E4257D7FE], 
sec-websocket-extensions=[permessage-deflate; client_max_window_bits], 
origin=[https://mywebsite.com], 
x-forwarded-for=[1.2.3.4], 
sec-websocket-version=[13], 
pragma=[no-cache], 
x-real-ip=[1.2.3.4], 
y-real-ip=[1.2.3.4], 
host=[mywebsite.com], 
connection=[Upgrade], 
sec-websocket-key=[ZroOewrBGrg660x7ocI+nog==], 
cache-control=[no-cache], 
accept-encoding=[gzip, deflate, br], 
user-agent=[Mozilla/5.0 (Linux; android 8.0.0; SM-N950F) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36]

并且该 IP 在我所在的区域附近可以进行地理定位。

【问题讨论】:

【参考方案1】:

这不是一项简单的任务,但您的配置看起来不错,您只需从您设置的标头 (X-Real-IP) 中获取原始 IP。

import javax.websocket.*;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig;

@ServerEndpoint(value = "/test", configurator = Test.TestConfiguratior.class)
public class Test extends Endpoint 

    @Override
    public void onOpen(Session session, EndpointConfig endpointConfig) 
        Object o = endpointConfig.getUserProperties().get(TestConfiguratior.HEADER_NAME);
        //o should be the string object you are looking for
    

    @OnMessage
    public void onMessage(Session session, String msg) 
        //do what u want
    

    public static class TestConfiguratior extends ServerEndpointConfig.Configurator 
        static String HEADER_NAME = "X-Real-IP";

        @Override
        public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) 
            String firstFoundHeader = request.getHeaders().get(HEADER_NAME.toLowerCase()).get(0);
            sec.getUserProperties().put(HEADER_NAME, firstFoundHeader);
        
    

TL.DR.:您可以使用javax.websocket.EndpointConfig#getUserProperties 映射将数据放入其中并稍后检索。最初我认为这可能会在使用这个确切的代码时导致一些并发问题,但我无法重现它。甚至文档也建议这样做: https://javaee.github.io/tutorial/websocket010.html

更新:这是一个JSR 356 示例,并非特定于上述框架。

【讨论】:

这会导致什么并发问题? 基本上ServerEndpointConfig.getUserProperties() 是“全局的”。如果有许多并发传入连接,您可以在读取之前覆盖此值。因此,在我看来,代码可以用于概念验证,但不适用于生产。如果您不想使用任何 CDI 内容,您也可以尝试使用 ThreadLocal,但我更愿意使用一些 Context。 您能否更新您的示例,说明您将如何在生产环境中实施它?一旦我们进入生产阶段,那就太好了,也将帮助人们在未来寻找这个。非常感谢。 是的,明天我会在我的机器前做。您在 CDI 容器中吗? 好吧,我试图重现并发问题,但没有运气......也许我记错了。甚至 JEE 文档也使用了我推荐的内容:javaee.github.io/tutorial/websocket010.html

以上是关于在 Websocket 通过 Nginx 转发用户 IP的主要内容,如果未能解决你的问题,请参考以下文章

解决nginx转发websocket报400错误

解决nginx转发websocket报400错误

Docker NGINX 代理不转发 Websocket

解决nginx转发websocket报400错误

解决nginx转发websocket报400错误

如何配置 nginx 以将 websocket 转发到不同的进程