Skip to content
Advertisement

Why I can StompCommand.CONNECT without JSESSIONID, but X-XSRF-TOKEN is required?

My goal is to secure a WebSocket endpoint e.g ws://localhost:8080/chat.

What I did:

  1. I tried to create WebSocket connection with STOMP

    var socket = new SockJS("/chat");
    stompClient = Stomp.over(socket);
    
    stompClient.connect({"X-XSRF-TOKEN": getCookie("XSRF-TOKEN")}, function (status) {
      // it should not execute.
    });
    

    AbstractSecurityWebSocketMessageBrokerConfigurer

    // see: https://docs.spring.io/spring-security/site/docs/current/reference/html5/#websocket
    @Configuration
    public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
       @Override
       protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
         messages
                .nullDestMatcher().authenticated()
                .simpDestMatchers("/app/**").hasRole("USER")
                .simpDestMatchers("/user/**", "/topic/channel/*").hasRole("USER")
                .simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll()
                .anyMessage().denyAll();
       }
    }
    

    WebSocketMessageBrokerConfigurer

    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
      @Override
      public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/chat").withSockJS(); // user connect to
      }
    
      @Override
      public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/app"); // user send message to
        registry.enableSimpleBroker("/topic", "/queue"); // user subscribe to .. for channel messages.
        registry.setUserDestinationPrefix("/user"); // user subscribe to .. for private messages.
      }
    }
    

    WebSecurityConfigurerAdapter

    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
       @Override
       protected void configure(HttpSecurity http) throws Exception {
         http
    
     .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                 .and()
                 .formLogin()
                 .and()
                 .authorizeRequests().anyRequest().authenticated();
       }
    }
    

My expected result is: The WebSocket connection should fail because I did not send the JSESSIONID.

My actual result is: The Spring Security recognize the user, and I can get the UserDetails with (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal().

My questions:

  1. If I wish to use Database, which should I store, the JSESSIONID or the XSRF-TOKEN ?

  2. Is using X-XSRF-TOKEN the right way to secure StompCommand.CONNECT endpoint?

  3. Why the Spring Security ask for X-XSRF-TOKEN instead of JSESSIONID. Isn’t CSRF token used to prevent Cross-Site Request Forgery?

    This is the error I get from the Browser’s console if I create a WebSocket connection without X-XSRF-TOKEN‘s key in the request header.

    <<< ERROR
    message:Failed to send message to ExecutorSubscribableChannel[clientInboundChannel]; nested exception 
    is org.springframework.security.web.csrf.InvalidCsrfTokenExceptionc Invalid CSRF Token 'null' was 
    found on the request parameter '_csrf' or header 'X-XSRF-TOKEN'.
    content-length:0
    

Advertisement

Answer

First, I will try to help you with WS connection. I tried configuration similar to yours and I was unable to connect to WS unauthorized. I confirmed that in browser there was no JSESSIONID cookie or when there was one, it was connected to anonymous session. Try setting logging of org.springframework.security to TRACE and follow written log, it will help you determine which filter authorised access, if that is the case.

For questions that you specified:

  1. JSESSIONID and XSRF-TOKEN are not something that are interchangeable. I believe that for same JSESSIONID you can receive different different XSRF-TOKEN cookies. So I think you should store session id. Spring has project for this: Spring Session JDBC

  2. I think that it is. Even Spring Security expects it by default. Spring Security Websocket

Any inbound CONNECT message requires a valid CSRF token to enforce Same Origin Policy

  1. I am not sure how familiar are you with the Cross-Site Request Forgery. If you are not, check this Spring CSRF, but in few sentences:

JSESSIONID is cookie and it will be sent with every request to your site, even from other sites. Because that is how browser works. So to prevent misusage of session, CSRF token is required. Because you are not using HTML to execute action, such as form submit, you need to pass this token some other way. Spring requires you to send it using Header named X-XSRF-TOKEN, because browser will not send it with every request like it would a cookie. Security is in that other site, which tries to exploit vulnerability, can not read cookie from your site and it can not use it to add CSRF in header for exploit request.

So, I think your solution is ok. Keep in mind, that HTTP and WS are different protocols, even if WS uses HTTP for handshake, but it adds multiple headers. Maybe because of that Spring ignores authority checks, but I think that you probably missed something and that spring security trace log will direct you what to check.

6 People found this is helpful
Advertisement