javascriptjavaspringwebsocketchat

WebSocket connection fails when deploying Spring web application to a server on my domain


I'm encountering an issue with WebSocket connectivity in my Spring web application. Locally, when I run the application, WebSocket connections are established successfully. However, upon deploying the application to a server on my domain, WebSocket connections fail to establish. The browser console displays the following error:

WebSocket connection to 'wss://merkeido.com/ws/776/1lq4jnfq/websocket' failed:

Additional Information:

What I've Tried:

  1. Ensuring WebSocket support is enabled in the Spring configuration.
  2. Verifying WebSocket endpoint URLs and configurations.
  3. Checking server logs for any relevant error messages.

Question: What could be causing the WebSocket connection to fail on deployment, specifically resulting in a 403 (Forbidden) error? Any insights or suggestions for troubleshooting would be greatly appreciated. Thank you!

WebSocket Configuration: Here is my WebSocket configuration class:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/user");
        config.setApplicationDestinationPrefixes("/app");
        config.setUserDestinationPrefix("/user");
    }

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

HTML/JavaScript: And here is my HTML/JavaScript code for establishing WebSocket connections:

<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>

<script th:inline="javascript"> 
    'use strict'; 
    
    let stompClient = null;
    let selectedUserId = null;
    let userId = user_id;
    
    connect();
    function connect() {
        const socket = new SockJS('/ws');
        stompClient = Stomp.over(socket);
    
        stompClient.connect({}, onConnected, onError);
    }
    
    function onConnected() {
        stompClient.subscribe(`/user/${userId+""}/queue/messages`, onMessageReceived);
        stompClient.subscribe(`/user/public`, onMessageReceived);
    
        // Register the connected user
        stompClient.send("/app/user.addUser",
            {},
            JSON.stringify({id: userId+"", c_status: 'ONLINE'})
        );
        
        findAndDisplayConnectedUsers().then();
    }
</script>

The actual error on my console:

Opening Web Socket...
websocket.js:6 WebSocket connection to 'wss://merkeido.com/ws/776/1lq4jnfq/websocket' failed: 
e.exports @ websocket.js:6
/ws/776/in3mqa1z/xhr_streaming?t=1714645083452:1 
        
        
       Failed to load resource: the server responded with a status of 403 ()
stomp.min.js:8 Web Socket Opened...
stomp.min.js:8 >>> CONNECT
accept-version:1.1,1.0
heart-beat:10000,10000

Solution

  • I found a solution to the problem by making a few adjustments to my WebSocket and WebSecurity configurations.

    WebSocket Configuration

    First, I edited my endpoint in the WebSocketConfig class to ensure allowed origins:

    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    
        @Override
        public void configureMessageBroker(MessageBrokerRegistry config) {
            config.enableSimpleBroker("/topic");
            config.setApplicationDestinationPrefixes("/app");
        }
    
        @Override
        public void registerStompEndpoints(StompEndpointRegistry registry) {
            // Ensure allowed origins
            registry.addEndpoint("/ws")
                    .setAllowedOrigins("https://domain-name.com")  // Replace with your client domain
                    .withSockJS();
        }
    }
    

    Web Security Configuration

    Next, I set up the WebSecurity configuration to handle CORS and CSRF:

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.cors().and().csrf().disable()
                .authorizeRequests()
                .antMatchers("/**").permitAll();  // Allow all requests temporarily
        }
    
        @Bean
        public CorsFilter corsFilter() {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            CorsConfiguration config = new CorsConfiguration();
            config.setAllowCredentials(true);
            config.addAllowedOrigin("https://domain-name.com");  // Replace with your client domain
            config.addAllowedHeader("*");
            config.addAllowedMethod("*");
            source.registerCorsConfiguration("/**", config);
            return new CorsFilter(source);
        }
    }
    

    Explanation WebSocket Configuration:

    The registerStompEndpoints method registers the /ws endpoint for STOMP over WebSocket and allows connections from https://domain-name.com using SockJS as a fallback option. Web Security Configuration:

    The configure method disables CSRF and enables CORS, allowing all requests temporarily. The corsFilter bean configures CORS to allow credentials and requests from https://domain-name.com, along with all headers and methods. And just like that, it works. This setup ensures that your WebSocket connection and security configurations are correctly aligned, allowing your client to connect without issues.