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.
@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:
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:
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:
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:
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.
Spring Webflux Websocket Security β Basic Authentication
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.