Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nginx Reverse Proxy Websocket Authentication - HTTP 403

I'm using Nginx as a reverse proxy of a Spring boot application. I also use Websockets with sockjs and stomp messages.

Here is the context configuration.

<websocket:message-broker application-destination-prefix="/app">
    <websocket:stomp-endpoint path="/localization" >
        <websocket:sockjs/>
    </websocket:stomp-endpoint>
    <websocket:simple-broker prefix="/topic" />
</websocket:message-broker>

Here is the client code:

var socket = new SockJS(entryPointUrl);
var stompClient = Stomp.over(socket);

var _this = this;

stompClient.connect({}, function () {
    stompClient.subscribe('/app/some-url', function (message) {
         // do some stuff
    });
});

I also you Spring Security to protect some content.

@Configuration
@Order(4)
public static class FrontendSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/js/**", "/css/**", "/webjars/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().loginPage("/login").permitAll()
                .and()
                .logout().permitAll();
    }

}

Everything works great, expect when I run this application behind a Nginx reverse proxy. Here is the reverse configuration:

    proxy_pass http://testsysten:8080;

    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # WebSocket support (nginx 1.4)
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;

    # Max body size
    client_max_body_size 10M;

The connection always fails with a HTTP 403 code.

I'm using version 1.9.7.

Do you have any idea, why the client does not gets authenticated?

I know similar questions, like this one but the solutions do not work at all.

Update

I managed to run the application over HTTP. I need to pass the CSRF token in the Nginx configuration. New configuration is:

    proxy_pass http://testsysten:8080;

    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

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

    # WebSocket support (nginx 1.4)
    proxy_http_version 1.1;

Only missing is redirect over HTTPS. In the Spring logs is see following entry:

o.s.w.s.s.t.h.DefaultSockJsService - Processing transport request: GET http://testsystem:80/localization/226/3mbmu212/websocket

Seems like Nginx Proxy needs to rewrite the to the right port.

like image 791
schrieveslaach Avatar asked Dec 07 '15 15:12

schrieveslaach


2 Answers

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";  
like image 129
schrieveslaach Avatar answered Oct 03 '22 21:10

schrieveslaach


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)

like image 35
Yann Vo Avatar answered Oct 03 '22 20:10

Yann Vo