title: Spring websocket authentication with spring security: 401 error
body: <p>I would like to authenticate WebSocket with <code>spring security</code>. From <a href="http://docs.spring.io/autorepo/docs/spring-security/4.1.x/reference/html/websocket.html" rel="nofollow noreferrer">spring official document</a> <code>23.2 WebSocket Authentication</code>, WebSocket will reuse the same authentication information that is found in the HTTP request when the WebSocket connection is made. So I setup spring security to authenticate <code>rest service</code>. If the user passes the rest authentication, it will have permission to WebSocket connection, otherwise it can't establish WebSocket connection. Following is the code:</p>
  <p><strong>Rest service for login: WssAuthService.java</strong></p>
  <pre><code>import org.springframework.web.bind.annotation.RequestMapping;
  import org.springframework.web.bind.annotation.RequestMethod;
  import org.springframework.web.bind.annotation.RestController;
  
  /**
   * Authentication service.
   */
  @RestController
  @RequestMapping(path = &quot;/dm&quot;)
  public class WssAuthService {
      
      @RequestMapping(path = &quot;/login&quot;, method = RequestMethod.GET)
      public String login(){
          return &quot;Login success to WssBroker...&quot;;
      }
  }
  </code></pre>
  <p><strong>spring security configuration: WebSecurityConfig.java</strong></p>
  <pre><code>import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.security.authentication.AuthenticationManager;
  import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  import org.springframework.security.config.http.SessionCreationPolicy;
   
  @Configuration
  @EnableWebSecurity
  public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
   
      public final static String REALM=&quot;MY_TEST_REALM&quot;;
       
      @Autowired
      public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
          auth.inMemoryAuthentication().withUser(&quot;admin&quot;).password(&quot;admin&quot;).roles(&quot;ADMIN&quot;)
          .and().withUser(&quot;test&quot;).password(&quot;test&quot;).roles(&quot;USER&quot;);
      }
       
      @Override
      protected void configure(HttpSecurity http) throws Exception {
          http.csrf().disable()
              .authorizeRequests()
              .anyRequest().authenticated()
              .and().httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint())
              .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);//We don't need sessions to be created.
      }
      
      @Bean
      public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint(){
          return new CustomBasicAuthenticationEntryPoint();
      }
      
      @Bean
      public AuthenticationManager authenticationManagerBean() throws Exception {
          // altough this seems like useless code,
          // its required to prevend spring boot auto-configuration
          return super.authenticationManagerBean();
      }
  
  }
  </code></pre>
  <p><strong>websocket server configuration: WssBrokerConfig.java</strong></p>
  <pre><code>import com.fasterxml.jackson.databind.ObjectMapper;
  import com.fasterxml.jackson.databind.SerializationFeature;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
  import org.springframework.messaging.simp.config.MessageBrokerRegistry;
  import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry;
  import org.springframework.security.config.annotation.web.socket.AbstractSecurityWebSocketMessageBrokerConfigurer;
  import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
  import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
  
  @Configuration
  @EnableWebSocketMessageBroker
  public class WssBrokerConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer  {
  
      @Override
      protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
          messages
              .nullDestMatcher().authenticated()
              .simpSubscribeDestMatchers(&quot;/topic/notification&quot;).permitAll()
              .simpDestMatchers(&quot;/**&quot;).authenticated()
  //            .simpSubscribeDestMatchers(&quot;/user/**&quot;, &quot;/topic/friends/*&quot;).hasRole(&quot;USER&quot;)
  //            .simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll()
              .anyMessage().denyAll();
  
      }
      
      @Override
      public void configureMessageBroker(MessageBrokerRegistry config) {
          config.enableSimpleBroker(&quot;/topic&quot;);
          config.setApplicationDestinationPrefixes(&quot;/ws&quot;);
      }
  
      @Override
      public void registerStompEndpoints(StompEndpointRegistry registry) {
          /**Note: setAllowedOrigins is important here: since we have both http &amp; websocket servers, cross-origin accesses should be enabled */
          registry.addEndpoint(&quot;/dm-ws&quot;).setAllowedOrigins(&quot;*&quot;).withSockJS();
      }
  
      @Bean
      public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
          ObjectMapper mapper = new ObjectMapper();
          mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
          MappingJackson2HttpMessageConverter converter =
                  new MappingJackson2HttpMessageConverter(mapper);
          return converter;
      }
  
  }
  </code></pre>
  <p><strong>websocket client:WebSocketClient.java</strong></p>
  <pre><code>import java.lang.reflect.Type;
  import java.util.ArrayList;
  import java.util.Base64;
  import java.util.List;
  import java.util.concurrent.CountDownLatch;
  
  import org.springframework.http.HttpEntity;
  import org.springframework.http.HttpHeaders;
  import org.springframework.http.HttpMethod;
  import org.springframework.http.ResponseEntity;
  import org.springframework.messaging.converter.MappingJackson2MessageConverter;
  import org.springframework.messaging.simp.stomp.StompCommand;
  import org.springframework.messaging.simp.stomp.StompFrameHandler;
  import org.springframework.messaging.simp.stomp.StompHeaders;
  import org.springframework.messaging.simp.stomp.StompSession;
  import org.springframework.messaging.simp.stomp.StompSessionHandler;
  import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
  import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
  import org.springframework.web.client.RestTemplate;
  import org.springframework.web.socket.client.WebSocketClient;
  import org.springframework.web.socket.client.standard.StandardWebSocketClient;
  import org.springframework.web.socket.messaging.WebSocketStompClient;
  import org.springframework.web.socket.sockjs.client.SockJsClient;
  import org.springframework.web.socket.sockjs.client.Transport;
  import org.springframework.web.socket.sockjs.client.WebSocketTransport;
  
  //@JsonIgnoreProperties(ignoreUnknown = true)
  public class WebsocketClient {
      public static void main(String[] args){
          // 1.login to rest service
          authToRest();
          // 2.establish websocket connection
          openConnection();
      }
      
      private static HttpHeaders getHeaders(){
          String plainCredentials=&quot;admin:admin&quot;;
          String base64Credentials = Base64.getEncoder().encodeToString(plainCredentials.getBytes());
           
          HttpHeaders headers = new HttpHeaders();
          headers.add(&quot;Authorization&quot;, &quot;Basic &quot; + base64Credentials);
          return headers;
      }
      
      public static void authToRest(){
          RestTemplate restTemplate = new RestTemplate(); 
          HttpEntity&lt;String&gt; request = new HttpEntity&lt;String&gt;(getHeaders());
          ResponseEntity&lt;String&gt; response = restTemplate.exchange(&quot;http://localhost:8082/dm/login&quot;, HttpMethod.GET, request, String.class);
          System.out.println(response.getBody());
      }
      
      public static void openConnection(){
          List&lt;Transport&gt; transports = new ArrayList&lt;&gt;(1);
          transports.add(new WebSocketTransport(new StandardWebSocketClient()));
          WebSocketClient transport = new SockJsClient(transports);
          WebSocketStompClient stompClient = new WebSocketStompClient(transport);
          
          stompClient.setMessageConverter(new MappingJackson2MessageConverter());
          ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
          taskScheduler.afterPropertiesSet();
          stompClient.setTaskScheduler(taskScheduler); // for heartbeats
          StompSessionHandler myHandler = new MyStompHandler();
          String url = &quot;ws://localhost:8082/dm-ws&quot;;
          stompClient.connect(url, myHandler);
          
          //block the thread
          CountDownLatch latch = new CountDownLatch(1);
          try {
              latch.await();
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      }
      
      private static class MyStompHandler extends StompSessionHandlerAdapter {
          
          @Override
          public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
              session.subscribe(&quot;/topic/response&quot;, new StompFrameHandler() {
                  @Override
                  public Type getPayloadType(StompHeaders headers) {
                      return Object.class;
                  }
                  @Override
                  public void handleFrame(StompHeaders headers, Object payload) {
                      System.out.println(payload);
                  }
              });
          }
  
          @Override
          public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload,
                  Throwable exception) {
              System.out.println(exception.getMessage());
          }
  
          @Override
          public void handleTransportError(StompSession session, Throwable exception) {
              exception.printStackTrace();
              System.out.println(&quot;transport error.&quot;);
          }
      }
  }
  </code></pre>
  <p>But the result turned out to be I can get the rest response but can't establish connection with WebSocket server. This is the error info:</p>
  <pre><code>14:31:35.368 [main] DEBUG org.springframework.web.client.RestTemplate - GET request for &quot;http://localhost:8082/dm/login&quot; resulted in 200 (null)
  14:31:35.369 [main] DEBUG org.springframework.web.client.RestTemplate - Reading [java.lang.String] as &quot;text/plain;charset=UTF-8&quot; using [org.springframework.http.converter.StringHttpMessageConverter@4b553d26]
  Login success to WssBroker...
  14:31:35.415 [main] INFO org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler - Initializing ExecutorService 
  14:31:35.569 [main] DEBUG org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport - Executing SockJS Info request, url=http://localhost:8082/dm-ws/info
  14:31:35.569 [main] DEBUG org.springframework.web.client.RestTemplate - Created GET request for &quot;http://localhost:8082/dm-ws/info&quot;
  14:31:35.574 [main] DEBUG org.springframework.web.client.RestTemplate - GET request for &quot;http://localhost:8082/dm-ws/info&quot; resulted in 401 (null); invoking error handler
  14:31:35.578 [main] ERROR org.springframework.web.socket.sockjs.client.SockJsClient - Initial SockJS &quot;Info&quot; request to server failed, url=ws://localhost:8082/dm-ws
  org.springframework.web.client.HttpClientErrorException: 401 null
      at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
      at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:667)
      at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:620)
      at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:595)
      at org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport.executeInfoRequestInternal(RestTemplateXhrTransport.java:138)
      at org.springframework.web.socket.sockjs.client.AbstractXhrTransport.executeInfoRequest(AbstractXhrTransport.java:155)
      at org.springframework.web.socket.sockjs.client.SockJsClient.getServerInfo(SockJsClient.java:286)
      at org.springframework.web.socket.sockjs.client.SockJsClient.doHandshake(SockJsClient.java:254)
      at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:274)
      at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:255)
      at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:235)
      at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:219)
      at com.hspi.dm.console.message.WebsocketClient.openConnection(WebsocketClient.java:66)
      at com.hspi.dm.console.message.WebsocketClient.main(WebsocketClient.java:35)
  14:31:35.583 [main] DEBUG org.springframework.messaging.simp.stomp.DefaultStompSession - Failed to connect session id=d8fda5d4-ba5a-7d22-f517-74e939096bfa
  org.springframework.web.client.HttpClientErrorException: 401 null
      at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
      at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:667)
      at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:620)
      at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:595)
      at org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport.executeInfoRequestInternal(RestTemplateXhrTransport.java:138)
      at org.springframework.web.socket.sockjs.client.AbstractXhrTransport.executeInfoRequest(AbstractXhrTransport.java:155)
      at org.springframework.web.socket.sockjs.client.SockJsClient.getServerInfo(SockJsClient.java:286)
      at org.springframework.web.socket.sockjs.client.SockJsClient.doHandshake(SockJsClient.java:254)
      at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:274)
      at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:255)
      at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:235)
      at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:219)
      at com.hspi.dm.console.message.WebsocketClient.openConnection(WebsocketClient.java:66)
      at com.hspi.dm.console.message.WebsocketClient.main(WebsocketClient.java:35)
  </code></pre>
  <p>Could someone help me? Thanks.</p>
  <h2>Update</h2>
  <p>From <a href="https://stackoverflow.com/questions/25640872/secure-spring-webscoket-using-spring-security-and-access-principal-from-websocke">this post</a>, the problem seems to be I didn't secure the <code>websocket endpoint</code>. I tried that two solutions but it didn't work, maybe I got the wrong way to do this. Wishing the correct way to secure <code>websocket endponit</code>.</p>