Open In App

Spring Webflux Websocket Security – Basic Authentication

Last Updated : 27 Mar, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

Spring WebFlux WebSockets, the authentication data that was included in the HTTP request at the time the WebSocket connection was established is reused. This indicates that WebSockets will receive the Principal on the HttpServletRequest. The Principal on the HttpServletRequest is automatically overridden if we are using Spring Security. More specifically, we just need to make sure to set up Spring Security to authenticate our HTTP-based web application in order to verify that a user has authenticated to our WebSocket application.

In this article, we will learn how to implement basic authentication in Spring Webflux Websocket Security.

Spring Webflux Websocket Security – Basic Authentication

Below is the Example of Spring Webflux Websocket Security – Basic Authentication:

Java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.PathMatchers;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.MatchResultMatchers;

@Configuration
@EnableWebFluxSecurity
public class WebSocketSecurityConfig {

    // Configure basic authentication with in-memory user details
    @Bean
    public MapReactiveUserDetailsService userDetailsService() {
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER")
                .build();
        return new MapReactiveUserDetailsService(user);
    }

    // Configure security for WebSocket endpoints
    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http
            .authorizeExchange()
                .matchers(ServerWebExchangeMatchers.pathMatchers("/ws/**"))
                .authenticated()
                .and()
            .httpBasic(Customizer.withDefaults());

        return http.build();
    }
}


Step-by-Step Implementation of Spring Webflux Websocket Security – Basic Authentication

Below are the steps to implement Spring Webflux Websocket Security.

Step 1: Add Maven Dependencies

Now let’s outline the general Spring Framework and Spring Security versions we will be utilizing:

<properties>
<spring.version>6.1.2</spring.version>
<spring-security.version>6.1.7</spring-security.version>
<spring-security-messaging.version>6.0.2</spring-security-messaging.version>
</properties>


Step 2: Authorization of WebSocket

We only need to publish an AuthorizationManager<Message<?>> and add the @EnableWebSocketSecurity annotation in order to configure authorization using Java Configuration. bean or the use-authorization-manager attribute in XML.

Java
@Configuration
@EnableWebSocketSecurity
public class WebSocketSecurityConfig {

    // This method creates a bean of type AuthorizationManager<Message<?>>.
    // The AuthorizationManager is responsible for handling message-level authorization.
    // It takes a MessageMatcherDelegatingAuthorizationManager.Builder as a parameter.
    @Bean
    AuthorizationManager<Message<?>> messageAuthorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
        // The following code configures message-level authorization rules.
        messages
            // Specify that messages with destination "/user/**" should have the "USER" role.
            .simpDestMatchers("/user/**").hasRole("USER");

        // Return the built MessageMatcherDelegatingAuthorizationManager.
        return messages.build();
    }
}


Step 3: Use the SpringSecurityMessaging Library

Using the spring-security-messaging framework, WebSocket-specific security is centered on the AbstractSecurityWebSocketMessageBrokerConfigurer class, and our project implements it like below:

Java
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.security.config.annotation.web.socket.AbstractSecurityWebSocketMessageBrokerConfigurer;
import org.springframework.security.config.annotation.web.socket.MessageSecurityMetadataSourceRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class SocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {

    /**
     * Configure message broker options.
     */
    @Override
    protected void configureMessageBroker(MessageBrokerRegistry registry) {
        // Enable a simple memory-based message broker to send messages to and receive messages from clients
        registry.enableSimpleBroker("/secured");
        
        // Set the prefix for destinations that the application is going to use
        registry.setApplicationDestinationPrefixes("/app");
    }

    /**
     * Configure security options for the WebSocket.
     */
    @Override
    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
        // Allow all messages to and from the "/app" destination (application-level)
        messages.simpDestMatchers("/app/**").permitAll();
        
        // Allow messages from all authenticated users to the "/secured/**" destination
        messages.simpDestMatchers("/secured/**").authenticated();
    }

    /**
     * Register STOMP endpoints for WebSocket communication.
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // Allow WebSocket connections on the "/socket" endpoint with SockJS fallback
        registry.addEndpoint("/socket")
                .setAllowedOrigins("*")
                .withSockJS();
    }
}


Step 4: Set up Socket Views and Controllers

To start, let’s configure our controllers and socket views for the essential Spring Security coverage:

Java
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@EnableWebSecurity
@ComponentScan("com.baeldung.springsecuredsockets")
public class SecurityConfig {

    /**
     * Configure the security filter chain.
     * Order of precedence is crucial; matching occurs from top to bottom.
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry ->
                authorizationManagerRequestMatcherRegistry
                        // Permit access to these URLs without authentication
                        .requestMatchers("/", "/index", "/authenticate").permitAll()
                        
                        // URLs under "/secured/" require authentication
                        .requestMatchers("/secured/**/**", "/secured/**/**/**", "/secured/socket", "/secured/success").authenticated()
                        
                        // Any other requests require authentication
                        .anyRequest().authenticated())
            
            // Configure form login settings
            .formLogin(httpSecurityFormLoginConfigurer ->
                    httpSecurityFormLoginConfigurer
                            .loginPage("/login").permitAll()  // Custom login page
                            .usernameParameter("username")  // Username parameter in the login form
                            .passwordParameter("password")  // Password parameter in the login form
                            .loginProcessingUrl("/authenticate")  // URL where the login form is submitted
                            .successHandler(loginSuccessHandler())  // Custom success handler
                            .failureUrl("/denied").permitAll())  // URL to redirect on login failure
            
            // Additional configurations can be added here, such as logout settings, CSRF protection, etc.
            // ...

        return http.build();
    }

    // You can define a custom login success handler bean if needed
    @Bean
    public AuthenticationSuccessHandler loginSuccessHandler() {
        return new CustomAuthenticationSuccessHandler();
    }

    // Other beans and configurations can be added as needed
}


Step 5: Provide Security Coverage

Now let’s construct an example socket controller and endpoint for which the security coverage was previously given:

Java
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

import java.text.SimpleDateFormat;
import java.util.Date;

@Controller
public class SocketController {

    // Handle incoming messages from the "/secured/chat" destination
    @MessageMapping("/secured/chat")
    
    // Send the processed message to the "/secured/history" destination
    @SendTo("/secured/history")
    public OutputMessage send(Message msg) throws Exception {
        // Create a new OutputMessage with the sender, message text, and timestamp
        return new OutputMessage(
                msg.getFrom(),
                msg.getText(),
                new SimpleDateFormat("HH:mm").format(new Date()));
    }
}


Step 6: Create SecurityFilterChain bean

Permitting iframes to utilize SockJS transports might be advantageous in some scenarios. We may construct a SecurityFilterChain bean to do this:

Java
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // Disable CSRF protection
        http.csrf(AbstractHttpConfigurer::disable)
            // Configure other security options as needed
            // ...

            // Configure security headers, disabling frame options
            .headers(httpSecurityHeadersConfigurer ->
                httpSecurityHeadersConfigurer.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)
            )
            
            // Configure authorization for HTTP requests using default settings
            .authorizeHttpRequests(Customizer.withDefaults());

        // Build and return the SecurityFilterChain
        return http.build();
    }
}
  • This uses Java-based settings to build up a SecurityFilterChain bean. It sets up default permission for HTTP requests.
  • By default, SockJS is set up to prevent transfers via HTML iframe elements. This is done to lessen the possibility of clickjacking.
  • Disables CSRF protection, and configures security headers (disabling frame options). Be careful to modify the setup in accordance with your unique security needs.


Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads