Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get/set the principal and session attributes from Spring 4 stomp websocket methods

I'm doing experiments with Spring 4 websockets and stomp, and I have a hard time figuring out how to get/set the current user and other session attributes in a message handling method annotated with @MessageMapping.

The documentation says that the message handling methods can take a Principal as argument, and I found that the principal is retrieved by Spring by calling getUserPrincipal() on the native socket session, and then associated with the socket session, but I haven't found any way to easily customize this behavior, other than writing a servlet filter and wrap the original request into a wrapper returning the principal found in my cookie.

So my questions are:

  1. How to manually set the principal to the socket session, when the client connects (I have this information thanks to a custom cookie, and I don't use Spring security)?
  2. If 1 is not possible, how to add additional attributes to the socket session when the client connects?
  3. How to access the socket session and its attributes from a message handling method?
  4. Is there a way to access the login and passcode sent by the browser at connection time. They seem to be completely ignore by Spring and not accessible.
like image 739
JB Nizet Avatar asked Dec 14 '13 10:12

JB Nizet


People also ask

Can spring WebSockets send Stomp messages to a single user?

Introduction In this tutorial, we'll describe how to use Spring WebSockets to send STOMP messages to a single user. That's important because we sometimes don't want to broadcast every message to every user.

How to maintain a WebSocket session in Spring Boot?

Hence, to maintain a websocket session, we require to intercept this HTTP request and keep the session id somewhere from where it can be accessed everytime whenever a websocket request is made. Here we will be using STOMP header attributes to track the session. 1. JDK 8 2. Spring Boot 3. Intellij Idea/ eclipse 4. Maven

What is HttpSession in Spring Boot?

It is a mechanism used by the Web container to store session information for a particular user. In this example we will be making use of HttpSession to achieve Session management. Also we will be using the Spring Session module

How do I authenticate a WebSocket application in spring?

To ensure a user has authenticated to your WebSocket application, all that is necessary is to ensure that you setup Spring Security to authenticate your HTTP based web application. To add authentication to your websocket connection, you can visit this nice explanation on spring official doc.


1 Answers

UPDATE: With Spring 4.1 it is possible to set the user on the handshake for #1 from above. Per the Spring documentation you can create a new class which extends DefaultHandshakeHandler and override the determineUser method. Additionally you can also create a security filter which sets the principal as well if you have a token. I have implemented the second one myself and I include some sample code for both below.

For #2 and #3 I do not think that it is possible still. For #4 Spring intentionally ignores these per the documentation here.

SAMPLE CODE FOR DefaultHandshakeHandler SUBCLASS:

@Configuration
@EnableWebSocketMessageBroker
public class ApplicationWebSocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer {

    public class MyHandshakeHandler extends DefaultHandshakeHandler {

        @Override
        protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, 
                                          Map<String, Object> attributes) {
            // add your own code to determine the user
            return null;
        }
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {

        registry.addEndpoint("/myEndPoint").setHandshakeHandler(new MyHandshakeHandler());

    }
}

SAMPLE CODE FOR SECURITY FILTER:

public class ApplicationSecurityTokenFilter extends GenericFilterBean {

    private final static String AUTHENTICATION_PARAMETER = "authentication";

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        if (servletRequest instanceof HttpServletRequest) {
            // check to see if already authenticated before trying again
            Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
            if ((existingAuth == null) || !existingAuth.isAuthenticated()) {
                HttpServletRequest request = (HttpServletRequest)servletRequest;
                UsernamePasswordAuthenticationToken token = extractToken(request);
                // dump token into security context (for authentication-provider to pick up)
                if (token != null) {  // if it exists
                    SecurityContextHolder.getContext().setAuthentication(token);
                }
            }
        }
        filterChain.doFilter(servletRequest,servletResponse);
    }

    private UsernamePasswordAuthenticationToken extractToken( HttpServletRequest request ) {
        UsernamePasswordAuthenticationToken authenticationToken = null;
        // do what you need to extract the information for a token
        // in this example we assume a query string that has an authenticate
        // parameter with a "user:password" string.  A new UsernamePasswordAuthenticationToken
        // is created and then normal authentication happens using this info.
        // This is just a sample and I am sure there are more secure ways to do this.
        if (request.getQueryString() != null) {
            String[] pairs = request.getQueryString().split("&");
            for (String pair : pairs) {
                String[] pairTokens = pair.split("=");
                if (pairTokens.length == 2) {
                    if (AUTHENTICATION_PARAMETER.equals(pairTokens[0])) {
                        String[] tokens = pairTokens[1].split(":");
                        if (tokens.length == 2) {
                            log.debug("Using credentials: " + pairTokens[1]);
                            authenticationToken = new UsernamePasswordAuthenticationToken(tokens[0], tokens[1]);
                        }
                    }
                }
            }
        }
        return authenticationToken;
    }
}

// set up your web security for the area in question
@Configuration
public class SubscriptionWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

    protected void configure(HttpSecurity http) throws Exception {
        http
                .requestMatchers().antMatchers("/myEndPoint**","/myEndPoint/**").and()
                .addFilterBefore(new ApplicationSecurityTokenFilter(), UsernamePasswordAuthenticationFilter.class)
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .httpBasic()  // leave this if you want non web browser clients to connect and add an auth header
                .and()
                .csrf().disable();
    }
}

** NOTE: ** DO NOT declare your filter as a Bean. If you do then it will also be picked up (at least using Spring Boot) in the generic filters so it will fire on every request.

like image 115
Rob Baily Avatar answered Sep 28 '22 08:09

Rob Baily