Building Real-Time Applications with Spring Boot and WebSockets
Traditional HTTP request-response mechanisms are often not sufficient for real-time applications due to the latency involved. WebSockets offer an efficient solution by enabling full-duplex, real-time communication between the server and the client.
In this article, we'll dive into what WebSockets are, how they work, and how you can integrate them into a Spring Boot application, along with a focus on securing WebSocket connections using Spring Security's updated API.
What are WebSockets?
WebSockets are a communication protocol that provides full-duplex, bidirectional communication channels over a single TCP connection. Unlike traditional HTTP, WebSockets allow the server and client to send messages to each other asynchronously without closing the connection after every exchange. This persistent connection is perfect for real-time use cases.
Key Characteristics:
- Persistent Connection: The WebSocket connection stays open until explicitly closed by the client or server, unlike HTTP where each request opens and closes a new connection.
- Full-Duplex: Both the client and server can communicate independently and simultaneously.
- Low Latency: WebSocket messages don’t have the overhead of HTTP, making them faster for real-time communication.
How WebSockets Work:
- Initial Handshake: The WebSocket protocol starts with an HTTP handshake initiated by the client. The server acknowledges the handshake and upgrades the connection to a WebSocket.
- Data Transmission: After the connection is established, data can flow in both directions without additional requests.
- Connection Maintenance: The connection remains open for real-time communication, only closing when either side decides to terminate it.
Setting Up WebSockets in Spring Boot
Spring Boot provides seamless WebSocket integration, enabling you to build real-time applications easily. Let's walk through the process of setting up WebSockets in a Spring Boot project.
Step 1: Add the WebSocket Dependency
First, add the necessary dependencies in your project. If you're using Maven, include the following in your pom.xml
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
For Gradle, add this line to your build.gradle
file:
implementation 'org.springframework.boot:spring-boot-starter-websocket'
Step 2: WebSocket Configuration
Next, create a configuration class to register WebSocket handlers. This class implements WebSocketConfigurer
to register WebSocket endpoints.
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new MyWebSocketHandler(), "/ws")
.setAllowedOrigins("*"); // Allow all origins, can be restricted in production
}
}
- @EnableWebSocket: Enables WebSocket support in the Spring Boot application.
- registerWebSocketHandlers: Registers WebSocket endpoints with specified URLs (e.g.,
/ws
in this case).
Step 3: WebSocket Handler
You need a handler to process incoming WebSocket messages from clients. Here's a basic implementation of a WebSocket handler by extending TextWebSocketHandler
.
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
public class MyWebSocketHandler extends TextWebSocketHandler {
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String clientMessage = message.getPayload();
System.out.println("Received: " + clientMessage);
// Responding back to the client
session.sendMessage(new TextMessage("Hello, " + clientMessage + "!"));
}
}
This handler processes incoming text messages. It reads the message, logs it, and then sends a response back to the client.
Step 4: Frontend WebSocket Client
To interact with the WebSocket server, you can create a simple WebSocket client using JavaScript in a web page.
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Test</title>
</head>
<body>
<script>
var ws = new WebSocket("ws://localhost:8080/ws");
ws.onopen = function() {
console.log("Connection opened");
ws.send("Hello from client");
};
ws.onmessage = function(event) {
console.log("Message received: " + event.data);
};
ws.onclose = function() {
console.log("Connection closed");
};
</script>
</body>
</html>
This JavaScript code opens a WebSocket connection, sends a message to the server, and logs responses from the server.
Broadcasting Messages to Multiple Clients
In many real-time applications (e.g., a chat application), you will need to broadcast messages to multiple clients. To do this, you can store all active WebSocket sessions and iterate through them when sending a broadcast.
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.util.ArrayList;
import java.util.List;
public class MyWebSocketHandler extends TextWebSocketHandler {
private List<WebSocketSession> sessions = new ArrayList<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
}
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String clientMessage = message.getPayload();
// Broadcast the message to all connected clients
for (WebSocketSession webSocketSession : sessions) {
if (webSocketSession.isOpen()) {
webSocketSession.sendMessage(new TextMessage("Broadcast: " + clientMessage));
}
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions.remove(session);
}
}
- afterConnectionEstablished: Adds new WebSocket sessions to the list when a client connects.
- handleTextMessage: Broadcasts incoming messages to all connected clients.
- afterConnectionClosed: Removes disconnected sessions.
Securing WebSockets with Spring Security (Updated)
With Spring Security 5.7+, WebSecurityConfigurerAdapter
has been deprecated. Here’s how to secure WebSocket endpoints using the new component-based approach with Spring Security.
Step 1: Add Spring Security Dependency
Add the following dependency for Spring Security in your pom.xml
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Step 2: Define a Security Configuration
In the new approach, you create a SecurityFilterChain
bean to define security rules for your WebSocket endpoints.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class WebSocketSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorizeRequests ->
authorizeRequests
.requestMatchers("/ws/**").authenticated() // Protect WebSocket endpoint
.anyRequest().permitAll()
)
.httpBasic(); // Enable basic authentication for simplicity
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
var user = User.withUsername("user")
.password("{noop}password") // No encoding for simplicity
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
This configuration secures the WebSocket endpoint (/ws/**
) and requires basic authentication for accessing it.
Real-World Use Cases for WebSockets
- Real-Time Chat Applications: Allow users to communicate instantly, with new messages appearing in real-time without refreshing the page.
- Live Notifications: Push notifications to users in real-time, such as social media notifications, stock price updates, or system alerts.
- Collaborative Tools: Collaborative platforms like Google Docs or Trello use WebSockets to ensure changes made by one user are reflected for others in real-time.
- Online Gaming: Multiplayer games rely heavily on WebSockets to sync game states and player actions across multiple clients in real-time.
- Financial Trading Platforms: Real-time stock prices, currency exchange rates, and trading activities are instantly updated using WebSockets.
Conclusion
WebSockets provide a powerful solution for building real-time, interactive applications. By leveraging Spring Boot's WebSocket support, you can easily integrate WebSockets into your applications to create chat systems, real-time notifications, and much more. With the recent changes in Spring Security, securing your WebSocket connections is more intuitive and aligned with modern security practices. Whether you're building a small real-time feature or a fully-fledged collaborative tool, WebSockets are an essential technology to master.