Nginx Reverse Proxy Websocket Authentication - HTTP 403

I solved the problem by myself. Basically, Nginx needs to pass some additional header values if you want to use Websocket and Spring Security. The following lines need to be added to location section in your Nginx config:

    # Pass the csrf token (see https://de.wikipedia.org/wiki/Cross-Site-Request-Forgery)
    # Default in Spring Boot and required. Without it nginx suppresses the value
    proxy_pass_header X-XSRF-TOKEN;

    # Set origin to the real instance, otherwise a of Spring security check will fail
    # Same value as defined in proxy_pass
    proxy_set_header Origin "http://testsysten:8080";  

The accepted solution did not work for me although I was using a very classical HTTPS configuration:

server {
    listen 443 ssl;
    location /ws {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $http_host;
        proxy_pass http://127.0.0.1:8888;
    }
...

The problem is that Spring checks the origin and specifically that code was causing me trouble:

// in org.springframework.web.util.UriComponentsBuilder.adaptFromForwardedHeaders(HttpHeaders):
        if ((this.scheme.equals("http") && "80".equals(this.port)) ||
                (this.scheme.equals("https") && "443".equals(this.port))) {
            this.port = null;
        }

In that code the scheme is 'http' and the port is 8888, which is not discarded because it is not the standard port.

The browser however hits https://myserver/ and the 443 port is omitted because it is the default HTTPS one.

Therefore the ports do not match (empty != 8888) and origin check fails.

Either you can disable origin checks in Spring WebSockets:

registry.addHandler( resgisterHandler(), "/ws" ).setAllowedOrigins( "*" );

or (probably safer) you can add the scheme and port to the NGINX proxy configuration:

    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Port $server_port;

If you are interested, those headers are read in

org.springframework.web.util.UriComponentsBuilder.adaptFromForwardedHeaders(HttpHeaders)

For Spring Boot 2.2.2+

Starting with Spring Boot version 2.2.2 you should be adding following setting for these X-Forwarded-* headers to be taken into account:

server.forward-headers-strategy=native

(in application.properties for instance)


I solved this problem without CSRF header in NGINX proxy.

My stack: spring-boot, spring-security (with redis session store), spring-boot-websocket with default STOMP implementation, NGINX to serve frontend and proxied to another services that frontend consume.

In first time I use the default configuration show in the NGINX Blog here and here (copy and paste for history):

http {
    map $http_upgrade $connection_upgrade {
        default upgrade;
        '' close;
    }

    upstream websocket {
        server 192.168.100.10:8010;
    }

    server {
        listen 8020;
        location / {
            proxy_pass http://websocket;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
        }
    }
}

But dont work, still 403 Forbidden.

I fixed this issue with the configuration below (the real important part to fix websocket is # WebSocket Proxy):

worker_processes  1;

events {
    worker_connections  1024;
}

http {

    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       30010;
        server_name  localhost;
        client_max_body_size 10M;

        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        }

        # Backend API Proxy
        location /api {
            proxy_pass http://192.168.0.100:30080;
            proxy_set_header Host $http_host;
            proxy_set_header Access-Control-Allow-Origin 192.168.0.100;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-NginX-Proxy true;
            rewrite ^/api/?(.*) /$1 break;
            proxy_redirect off;
        }

        # CDN Proxy
        location ~ ^/cdn/(.*) {
            proxy_pass http://192.168.0.110:9000;
            rewrite ^/cdn/(.*) /$1 break;
        }

        # This is the configuration that fix the problem with WebSocket
        # WebSocket Proxy
        location /ws {
            proxy_pass http://192.168.0.120:30090;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $http_host;
            proxy_set_header Access-Control-Allow-Origin 192.168.0.120;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-NginX-Proxy true;
        }

    }

}