spring-projects / spring-session

Spring Session
https://spring.io/projects/spring-session
Apache License 2.0
1.86k stars 1.11k forks source link

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

Open jasonrichdarmawan opened 3 years ago

jasonrichdarmawan commented 3 years ago

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

marcusdacoregio commented 1 year ago

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);
        }
    }

    // ...

}