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> <springframework.version>4.3.8.RELEASE</springframework.version> <springsecurity.version>4.2.3.RELEASE</springsecurity.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>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${springsecurity.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${springsecurity.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 required dependencies:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-messaging</artifactId> <version>${springsecurity.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
WebSocket-specific security using the spring-security-messaging library centers on the AbstractSecurityWebSocketMessageBrokerConfigurer class and its implementation within your project:
@Configuration public class SocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { //... }
The AbstractSecurityWebSocketMessageBrokerConfigurer class provides additional security coverage provided by WebSecurityConfigurerAdapter.
The spring-security-messaging library is not the only way to implement security for WebSockets. If we stick with the ordinary spring-websocket library, we can implement the WebSocketConfigurer interface and attach security interceptors to our socket handlers.
Since we are using the spring-security-messaging library, we will use the AbstractSecurityWebSocketMessageBrokerConfigurer approach.
3.1. Implementing configureInbound()
The implementation of configureInbound() is the most important step in configuring your AbstractSecurityWebSocketMessageBrokerConfigurer subclass:
@Override protected void configureInbound( MessageSecurityMetadataSourceRegistry messages) { messages .simpDestMatchers("/secured/**").authenticated() .anyMessage().authenticated(); }
Whereas the WebSecurityConfigurerAdapter lets you specify various application-wide authorization requirements for different routes, AbstractSecurityWebSocketMessageBrokerConfigurer allows you to specify the specific authorization requirements for socket destinations.
3.2. Type and Destination Matching
MessageSecurityMetadataSourceRegistry allows us to specify security constraints like paths, user roles, and which messages are allowed.
Type matchers constrain which SimpMessageType are allowed and in what way:
.simpTypeMatchers(CONNECT, UNSUBSCRIBE, DISCONNECT).permitAll()
Destination matchers constrain which endpoint patterns are accessible and in what way:
.simpDestMatchers("/app/**").hasRole("ADMIN")
Subscribe destination matchers map a List of SimpDestinationMessageMatcher instances that match on SimpMessageType.SUBSCRIBE:
.simpSubscribeDestMatchers("/topic/**").authenticated()
Here is the complete list of all available methods for type and destination matching.
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 extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/index", "/authenticate").permitAll() .antMatchers( "/secured/**/**", "/secured/success", "/secured/socket", "/secured/success").authenticated() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login").permitAll() .usernameParameter("username") .passwordParameter("password") .loginProcessingUrl("/authenticate") //... } }
Second, let’s set up the actual message destination with authentication requirements:
@Configuration public class SocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { @Override protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { messages .simpDestMatchers("/secured/**").authenticated() .anyMessage().authenticated(); } }
Now, in our AbstractWebSocketMessageBrokerConfigurer, we can register the actual message and STOMP endpoints:
@Configuration @EnableWebSocketMessageBroker public class SocketBrokerConfig extends AbstractWebSocketMessageBrokerConfigurer { @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.
However, 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
It is common to use STOMP along with SockJS to implement client-side support for Spring WebSockets.
SockJS is configured to disallow transports through HTML iframe elements by default. 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 override the default configuration in WebSecurityConfigurerAdapter:
@Override protected void configure(HttpSecurity http) throws Exception { http .csrf() //... .and() .headers() .frameOptions().sameOrigin() .and() .authorizeRequests(); }
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 to if you are looking to learn more about this integration.
As always, check our Github project for examples used in this article.