spring-session icon indicating copy to clipboard operation
spring-session copied to clipboard

Add support for Session in X-Auth-Token header for WebSocket endpoint, currently only REST endpoint is supported by `X-Auth-Token` header

Open jasonrichdarmawan opened this issue 4 years ago • 1 comments

Expected Behavior

The Spring Session should authenticate session by getting the X-Auth-Token header's value for the WebSocket endpoint. Currently, only REST endpoint that authenticates session if the X-Auth-Token header is present.

Current Behavior

The Spring Session ignores the X-Auth-Token header for the WebSocket endpoint in CONNECT frame. This issue causes every WebSocket connection to be anoymous.

e.g in REST endpoint

User send request to GET /helloworld with X-Auth-Token header. The server recognize the token and return the response with body "Hello World"

e.g in WebSocket endpoint

  1. User send a HTTP handshake to GET /chat.
  2. User send a CONNECT frame with X-Auth-Token. But currently there is no way to retrieve the Principal from the X-Auth-Token, so every request is an anonymousUser.

Client is written in JavaScript and use STOMP

    let stompClient = null;

    const stompConfig = {
      brokerURL: "ws://localhost:8080/chat",
      connectHeaders: {
        "X-AUTH-TOKEN": X_Auth_Token,
       },
    };

    stompClient = new StompJs.Client(stompConfig);
    stompClient.activate();

The current Spring Session docs to override HttpSession implementation does not explain how to use the X-Auth-Token for the WebSocket. In fact, because the Cookie-based authentication is disabled after overriding the HttpSession implementation, there is no way to authenticate user for the WebSocket endpoint using the HttpSession.

To provide session in X-Auth-Token header, you can override the HttpSession with an annotation

// see: https://docs.spring.io/spring-session/docs/current/reference/html5/#httpsession-rest
@Configuration
// Override HttpSession's Filter, in this instance Spring Session is backed by Redis.
@EnableRedisHttpSession
public class HttpSessionConfig {

  // Default connection configuration, to localhost:6739.
  @Bean
  public LettuceConnectionFactory connectionFactory() {
    return new LettuceConnectionFactory();
  }

  // Tell Spring to use HTTP headers, X-Auth-Token.
  @Bean
  public HttpSessionIdResolver httpSessionIdResolver() {
    return HeaderHttpSessionIdResolver.xAuthToken();
  }
}

Context

How has this issue affected you? This is an issue because if you use annotation @EnableRedisHttpSession both Browser and Mobile Apps can't authenticate when connecting to a WebSocket endpoint. By default, Spring Security provides you with Cookie-based authentication and Spring can authenticate both for REST endpoint and WebSocket endpoint because the Cookie e.g JSESSIONID / SESSION and XSRF-TOKEN is always there for each HTTP handshake, including the HTTP handshake for the WebSocket connection.

What are you trying to accomplish? Get the Principal by using the X-Auth-Token header's value

@Configuration
@EnableWebSocketMessageBroker
// see: https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#websocket-stomp-authentication-token-based
@Order(Ordered.HIGHEST_PRECEDENCE + 99)
public class WebSocketAuthenticationConfig implements WebSocketMessageBrokerConfigurer {
  @Override
  public void configureClientInboundChannel(ChannelRegistration registration) {
    registration.interceptors(new ChannelInterceptor() {
      @Override
      public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor =
                MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        if (StompCommand.CONNECT.equals(accessor.getCommand())) {
          String sessionId = accessor.getFirstNativeHeader("X-AUTH-TOKEN");
//          accessor.setUser(user);
        }
        return message;
      }
    });
  }
}

What other alternatives have you considered? Use JWT

Are you aware of any workarounds? As per my knowledge about HttpSession, currently there is no workarounds

jasonrichdarmawan avatar Jan 29 '21 17:01 jasonrichdarmawan

Hi @jasonrichdarmawan,

Have you tried creating a DelegatingHttpSessionIdResolver that could try to resolve the session id from the cookie, and, if not present, try to resolve it from the HTTP header? Something like:

public class DelegatingHttpSessionIdResolver implements HttpSessionIdResolver {

    CookieHttpSessionIdResolver cookieResolver = new CookieHttpSessionIdResolver();
    HeaderHttpSessionIdResolver headerResolver =  HeaderHttpSessionIdResolver.xAuthToken();

    @Override
    public List<String> resolveSessionIds(HttpServletRequest request) {
	List<String> cookieSessionIds = this.cookieResolver.resolveSessionIds(request);
        if (cookieSessionIds == null || cookieSessionIds.isEmpty()) {
            return this.headerResolver.resolveSessionIds(request);
        }
    }

    // ...

}

marcusdacoregio avatar Mar 23 '23 19:03 marcusdacoregio