Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Secured WebSocket upgrade over STOMP via SockJS fails with Invalid Upgrade header null

I am working on a web application that uses Spring Security and WebSockets. I am able to use the WebSockets without issue from my local machine, running the Spring Boot app from the JAR with embedded Tomcat. However, when I upload the same JAR/project to CloudFoundry or OpenShift (and run it as an executable JAR), the protocol upgrade at the time of establishing the WebSocket connection fails.

I made a small sample project that demonstrates this issue (at least when I try it on my machine or my CloudFoundry or OpenShift account). It is available here: https://github.com/shakuzen/spring-stomp-websocket-test

This is a stripped-down, bare-bones example, but I am able to consistently recreate the issue. The error message in the logs is:

2014-10-20T00:46:36.69+0900 [App/0]   OUT 2014-10-19 15:46:36.698 DEBUG 32 --- [io-61088-exec-5] o.s.w.s.s.s.DefaultHandshakeHandler      : Invalid Upgrade header null

The DEBUG logs for DefaultHandshakeHandler before that show that the Upgrade header is missing. However, if you look at the request sent using Chrome's developer tools (or any browser's equivalent tool), you will see that the request is different. The following two requests were sent.

1

GET /hello/info HTTP/1.1
Host: sswss-test.cfapps.io
Connection: keep-alive
Authorization: Basic dGVzdHVzZXI6dGVzdHBhc3M=
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.104 Safari/537.36
Accept: */*
Referer: http://sswss-test.cfapps.io/message
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,ja;q=0.6
Cookie: __VCAP_ID__=693dd6ff1b494f88a2c8567590da500dc44b4818746a45b28dd98a29b2607395; JSESSIONID=C5485065FE0A1DBCDF1F148A63D08FC2
DNT: 1

2

GET ws://sswss-test.cfapps.io/hello/863/olm1kojs/websocket HTTP/1.1
Host: sswss-test.cfapps.io
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Authorization: Basic dGVzdHVzZXI6dGVzdHBhc3M=
Upgrade: websocket
Origin: http://sswss-test.cfapps.io
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.104 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,ja;q=0.6
Cookie: __VCAP_ID__=693dd6ff1b494f88a2c8567590da500dc44b4818746a45b28dd98a29b2607395; JSESSIONID=C5485065FE0A1DBCDF1F148A63D08FC2
Sec-WebSocket-Key: 7bW1pg6f9axkVfqV21k/9w==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

It seems it is taking the first request's headers and failing due to that. However, the same two requests (of course with localhost instead of sswss-test.cf.apps.io and different values for security headers) are sent when this is run on my local machine and it does not have this issue. I have tried this on Chrome and Firefox.

The GitHub project I linked is using Spring Boot 1.2.0.M2, but I also tested with the latest release version (1.1.8.RELEASE) and got the same results. I also found in searching that perhaps SockJS does not handle relative URLs very well, so I tried from the console running the connect command with the absolute URL:

var socket2 = new SockJS('http://sswss-test.cfapps.io/hello');

But unfortunately the result was the same.

Any suggestions or solutions are greatly appreciated. Feel free to fork the GitHub project and mess around with it (OpenShift has a free account option, so you can deploy there for free to recreate the issue). I will copy the relevant portions of the project here.

WebSocketConfig

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/queue/", "/topic/");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/hello").withSockJS();
    }
}

SecurityConfig

My actual application is using Facebook for authentication, but I was able to reproduce the issue with basic authentication, so I didn't want to waste any more time doing additional setup and adding complexity.

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .httpBasic()
            .and()
            //Configures url based authorization
            .authorizeRequests()
                // Anyone can access the urls
                .antMatchers("/").permitAll()
                //The rest of the our application is protected.
                .antMatchers("/**").authenticated();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("testuser").password("testpass").roles("USER").and()
                .withUser("adminuser").password("adminpass").roles("ADMIN","USER");
    }
}
like image 925
Tommy Ludwig Avatar asked Oct 19 '14 16:10

Tommy Ludwig


1 Answers

The issue was port restrictions on OpenShift with their support of websockets at the time. See their blog post https://blog.openshift.com/paas-websockets/ which mentions:

So, for plain WebSockets ws:// you will use port 8000 and for secured connections wss:// port 8443.

Rossen from the Spring Framework team figured this out when I opened SPR-12371.

There is another answer on Stack Overflow with the same answer: https://stackoverflow.com/a/19952072

like image 116
Tommy Ludwig Avatar answered Oct 13 '22 17:10

Tommy Ludwig