6

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");
    }
}
Tommy Ludwig
  • 543
  • 7
  • 11
  • 1
    so any luck with this? im also looking to resolve it – naoru Oct 24 '14 at 21:05
  • I apologize for taking so long to respond. I've been busy with work and moving that I haven't had a chance to write a proper response. For me, the issue was ports. OpenShift and CloudFoundry only allow websocket connections on specific ports. This is why it works on my local machine that has no such restrictions. Simply adding the specific port to the URL allows things to work, but then makes the URL ugly. This is the next thing I want to find a solution for, if possible. I will try to write up a proper answer with links in the next few days. – Tommy Ludwig Nov 05 '14 at 00:15
  • @TommyLudwig Ahhh! This is exactly the thing Stack Overflow was hoping to help eliminate -- you have a problem, you search and search, find someone with the same problem. They pop on a forum thread 4 years ago and say, "nevermind, I figured it out." What did you figure out, man? Help us out here. – Chris Baker Feb 18 '19 at 02:53

1 Answers1

1

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

Tommy Ludwig
  • 543
  • 7
  • 11