Let's get started with a Microservice Architecture with Spring Cloud:
Intro to Security and WebSockets
Last updated: January 8, 2024
1. Introduction
In a previous article, we showed how to add WebSockets to a Spring MVC project.
Here, we’ll describe how to add security to Spring WebSockets in Spring MVC. Before continuing, make sure you already have basic Spring MVC Security coverage in place – if not, check out this article.
2. Maven Dependencies
There are two main groups of Maven dependencies we need for our WebSocket implementation.
First, let’s specify the overarching versions of the Spring Framework and Spring Security that we will be using:
<properties>
<spring.version>6.0.12</spring.version>
<spring-security.version>6.1.5</spring-security.version>
<spring-security-messaging.version>6.0.2</spring-security-messaging.version>
</properties>
Second, let’s add the core Spring MVC and Spring Security libraries required to implement basic authentication and authorization:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring-security.version}</version>
</dependency>
The latest versions of spring-core, spring-web, spring-webmvc, spring-security-web, spring-security-config can be found on Maven Central.
Lastly, let’s add the required dependencies:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-messaging</artifactId>
<version>${spring-security-messaging.version}</version>
</dependency>
You can find the latest version of spring-websocket, spring-messaging, and spring-security-messaging on Maven Central.
3. Basic WebSocket Security
To configure WebSocket Security, include the @EnableWebSocketSecurity annotation and publish an AuthorizationManager<Message<?>> bean. This can be done by using the AuthorizationManagerMessageMatcherRegistry to specify endpoint patterns like :
@Configuration
@EnableWebSocketSecurity
public class SocketSecurityConfig {
@Bean
AuthorizationManager<Message<?>> messageAuthorizationManager(
MessageMatcherDelegatingAuthorizationManager.Builder messages) {
messages.simpDestMatchers("/secured/**", "/secured/**/**")
.authenticated()
.anyMessage()
.authenticated();
return messages.build();
}
}
4. Securing Socket Routes
Now that we’ve been introduced to basic socket security and type matching configuration, we can combine socket security, views, STOMP (a text-messaging protocol), message brokers, and socket controllers to enable secure WebSockets within our Spring MVC application.
First, let’s set up our socket views and controllers for basic Spring Security coverage:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@EnableWebSecurity
@ComponentScan("com.baeldung.springsecuredsockets")
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry ->
authorizationManagerRequestMatcherRegistry
.requestMatchers("/", "/index", "/authenticate").permitAll()
.requestMatchers("/secured/**/**", "/secured/**/**/**", "/secured/socket",
"/secured/success").authenticated()
.anyRequest().authenticated())
.formLogin(httpSecurityFormLoginConfigurer ->
httpSecurityFormLoginConfigurer.loginPage("/login").permitAll()
.usernameParameter("username")
.passwordParameter("password")
.loginProcessingUrl("/authenticate")
.successHandler(loginSuccessHandler())
.failureUrl("/denied").permitAll())
//...
}
}
Second, let’s set up the actual message destination with authentication requirements:
@Configuration
@EnableWebSocketSecurity
public class SocketSecurityConfig {
@Bean
AuthorizationManager<Message<?>> messageAuthorizationManager(
MessageMatcherDelegatingAuthorizationManager.Builder messages) {
messages.simpDestMatchers("/secured/**", "/secured/**/**")
.authenticated()
.anyMessage()
.authenticated();
return messages.build();
}
}
Now, in our WebSocketMessageBrokerConfigurer, we can register the actual message and STOMP endpoints:
@Configuration
@EnableWebSocketMessageBroker
public class SocketBrokerConfig
implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/secured/history");
config.setApplicationDestinationPrefixes("/spring-security-mvc-socket");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/secured/chat")
.withSockJS();
}
}
Let’s define an example socket controller and endpoint that we provided security coverage for above:
@Controller
public class SocketController {
@MessageMapping("/secured/chat")
@SendTo("/secured/history")
public OutputMessage send(Message msg) throws Exception {
return new OutputMessage(
msg.getFrom(),
msg.getText(),
new SimpleDateFormat("HH:mm").format(new Date()));
}
}
5. Same Origin Policy
The Same Origin Policy requires that all interactions with an endpoint must come from the same domain where the interaction was initiated.
For example, suppose your WebSockets implementation is hosted at foo.com, and you are enforcing same origin policy. If a user connects to your client hosted at foo.com and then opens another browser to bar.com, then bar.com will not have access to your WebSocket implementation.
5.1. Overriding the Same Origin Policy
Spring WebSockets enforce the Same Origin Policy out of the box, while ordinary WebSockets do not.
In fact, Spring Security requires a CSRF (Cross Site Request Forgery) token for any valid CONNECT message type:
@Controller
public class CsrfTokenController {
@GetMapping("/csrf")
public @ResponseBody String getCsrfToken(HttpServletRequest request) {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
return csrf.getToken();
}
}
By calling the endpoint at /csrf, a client can acquire the token and authenticate through the CSRF security layer.
CSRF is not configurable when using @EnableWebSocketSecurity, though this will likely be added in a future release. However, if you are using the legacy AbstractSecurityWebSocketMessageBrokerConfigurer, the Same Origin Policy for Spring can be overridden by adding the following configuration to your AbstractSecurityWebSocketMessageBrokerConfigurer:
@Override
protected boolean sameOriginDisabled() {
return true;
}
5.2. STOMP, SockJS Support, and Frame Options
Using STOMP and SockJS to implement client-side support for Spring WebSockets is common.
By default, SockJS is configured to disallow transports through HTML iframe elements. This is to prevent the threat of clickjacking.
However, there are certain use-cases where allowing iframes to leverage SockJS transports can be beneficial. To do so, you can create SecurityFilterChain bean:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http)
throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
//...
.headers(headers -> headers.frameOptions(frameOptions -> frameOptions.sameOrigin()))
.authorizeHttpRequests(Customizer.withDefaults());
return http.build();
}
Note that in this example, we follow the Same Origin Policy despite allowing transports through iframes.
6. Oauth2 Coverage
Oauth2-specific support for Spring WebSockets is made possible by implementing Oauth2 security coverage in addition to — and by extending — your standard WebSecurityConfigurerAdapter coverage. Here’s an example of how to implement Oauth2.
To authenticate and gain access to a WebSocket endpoint, you can pass an Oauth2 access_token into a query parameter when connecting from your client to your back-end WebSocket.
Here’s an example demonstrating that concept using SockJS and STOMP:
var endpoint = '/ws/?access_token=' + auth.access_token;
var socket = new SockJS(endpoint);
var stompClient = Stomp.over(socket);
7. Conclusion
In this brief tutorial, we have shown how to add security to Spring WebSockets. Take a look at Spring’s WebSocket and WebSocket Security reference documentation if you are looking to learn more about this integration.
The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
















