Distributed Session Management with Spring Boot and Spring Session
Introduction
Managing sessions in a distributed environment is a common challenge in modern web applications. Spring Session provides a robust solution to this problem, allowing session data to be stored in different types of persistent storage, such as relational databases, Redis, and Hazelcast. This article explores the theoretical components of Spring Session, practical implementation examples, and a real-world use case.
What is a Spring Session?
Spring Session is a Spring project that provides an API and implementations for managing user-session information. It replaces the standard HttpSession
with a more robust implementation that allows session persistence in various types of storage, such as Redis, JDBC (relational databases), and Hazelcast. The main goal of Spring Session is to solve common problems in distributed environments, such as data consistency and scalability.
Key Attributes of the Spring Session Class
sessionId: Unique identifier of the session.
creationTime: Timestamp indicating when the session was created.
lastAccessedTime: Timestamp indicating the last time the session was accessed.
maxInactiveInterval: Maximum inactivity interval before the session is invalidated.
attributes: Map containing session attributes.
What is an HttpSession?
HttpSession
is an interface in the Java Servlet API that allows storing session information between different HTTP requests. The session is created by the web server when a user first accesses a web application and lasts until it is explicitly ended or expires due to inactivity.
Theoretical Components of Spring Session
Spring Session Core:
Provides APIs and implementations for managing user session information.
Supports
HttpSession
,WebSession
, andWebSocket
.
Spring Session Data Redis:
Session repository implementation for managing sessions in Redis.
Redis is used for its speed and efficiency in handling in-memory data.
Spring Session JDBC:
Session repository implementation for relational databases like MySQL.
Allows session persistence in a relational database, facilitating backup and recovery.
Spring Session Hazelcast:
Session repository implementation for Hazelcast.
Hazelcast is a distributed in-memory data grid that supports scalability and high availability.
Transparent Integration with Spring Boot:
Spring Boot’s auto-configuration allows effortless integration with Spring Session.
Use annotations like
@EnableRedisHttpSession
to enable session management with Redis.
Simple Example 1: Spring Session with Redis
Project Configuration
Adding Dependencies in pom.xml
:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring-session-data-redis
: Dependency required to integrate Spring Session with Redis.spring-boot-starter-data-redis
: Spring Boot starter that simplifies Redis configuration.
Redis Configuration in application.properties
:
spring.session.store-type=redis
spring.redis.host=localhost
spring.redis.port=6379
spring.session.store
-type
: Defines the session storage type as Redis.spring.redis.host
: Redis server address.spring.redis.port
: Redis server port.
Main Configuration Class:
@SpringBootApplication
public class SpringBootSessionApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootSessionApplication.class, args);
}
}
@SpringBootApplication
: Annotation that marks the main class of a Spring Boot application.SpringApplication.run
: Method to start the Spring Boot application.
Detailed Explanation of the Session Controller Code
The code below is a Spring MVC controller that handles HTTP sessions to store and retrieve user messages. Let's break down line by line what each part of the code does.
@Controller
public class SessionController {
@GetMapping("/")
public String home(HttpSession session, Model model) {
List<String> messages = (List<String>) session.getAttribute("MY_SESSION_MESSAGES");
if (messages == null) {
messages = new ArrayList<>();
}
model.addAttribute("sessionMessages", messages);
return "index";
}
@PostMapping("/message")
public String saveMessage(@RequestParam("message") String message, HttpSession session) {
List<String> messages = (List<String>) session.getAttribute("MY_SESSION_MESSAGES");
if (messages == null) {
messages = new ArrayList<>();
session.setAttribute("MY_SESSION_MESSAGES", messages);
}
messages.add(message);
session.setAttribute("MY_SESSION_MESSAGES", messages);
return "redirect:/";
}
}
Detailed Explanation
@Controller
Annotation:- Indicates that the
SessionController
class is a Spring MVC controller, responsible for handling HTTP requests.
- Indicates that the
home
Method:@GetMapping("/") public String home(HttpSession session, Model model) { List<String> messages = (List<String>) session.getAttribute("MY_SESSION_MESSAGES"); if (messages == null) { messages = new ArrayList<>(); } model.addAttribute("sessionMessages", messages); return "index"; }
@GetMapping("/")
: Maps HTTP GET requests to the root path ("/"
) to thehome
method.HttpSession session
: Injects the current HTTP session into the method.Model model
: Injects the model to be passed to the view.session.getAttribute("MY_SESSION_MESSAGES")
: Attempts to retrieve the list of messages stored in the session under the key"MY_SESSION_MESSAGES"
.if (messages == null) { messages = new ArrayList<>(); }
: If the list of messages does not exist in the session (isnull
), initializes a new empty list.model.addAttribute("sessionMessages", messages);
: Adds the list of messages to the model with the key"sessionMessages"
to be used in the view.return "index";
: Returns the view name"index"
for rendering.
saveMessage
Method:@PostMapping("/message") public String saveMessage(@RequestParam("message") String message, HttpSession session) { List<String> messages = (List<String>) session.getAttribute("MY_SESSION_MESSAGES"); if (messages == null) { messages = new ArrayList<>(); session.setAttribute("MY_SESSION_MESSAGES", messages); } messages.add(message); session.setAttribute("MY_SESSION_MESSAGES", messages); return "redirect:/"; }
@PostMapping("/message")
: Maps HTTP POST requests to the path/message
to thesaveMessage
method.@RequestParam("message") String message
: Injects themessage
parameter from the HTTP request into the method.HttpSession session
: Injects the current HTTP session into the method.session.getAttribute("MY_SESSION_MESSAGES")
: Attempts to retrieve the list of messages stored in the session under the key"MY_SESSION_MESSAGES"
.if (messages == null) { messages = new ArrayList<>(); session.setAttribute("MY_SESSION_MESSAGES", messages); }
: If the list of messages does not exist in the session (isnull
), initializes a new empty list and stores it in the session under the key"MY_SESSION_MESSAGES"
.messages.add(message);
: Adds the new message to the list.session.setAttribute("MY_SESSION_MESSAGES", messages);
: Updates the list of messages in the session under the key"MY_SESSION_MESSAGES"
.return "redirect:/";
: Redirects the request to the root path ("/"
), triggering thehome
method again to display the updated list of messages.
Key Points
MY_SESSION_MESSAGES
: This is the key used to store and retrieve the list of messages in the HTTP session. Initially, this key may not exist in the session, which is why the code checks if the returned value isnull
and initializes a new list if necessary.session.getAttribute
andsession.setAttribute
: Methods used to manipulate session attributes. They allow storing and retrieving data that persists across multiple HTTP requests from the same user.Redirect (
redirect:/
): After saving a new message, the application redirects the user to the root path, where the list of messages is retrieved and displayed in the"index"
view.
Simple Example 2: Spring Session with JDBC
Project Configuration
Adding Dependencies in pom.xml
:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
spring-session-jdbc
: Dependency to integrate Spring Session with JDBC.mysql-connector-java
: JDBC driver for MySQL.
Database Connection Configuration in application.properties
:
spring.datasource.url=jdbc:mysql://localhost:3306/spring_session
spring.datasource.username=root
spring.datasource.password=secret
spring.session.store-type=jdbc
spring.datasource.url
: URL for connecting to the MySQL database.spring.datasource.username
: Username for database access.spring.datasource.password
: Password for database access.spring.session.store
-type
: Defines the session storage type as JDBC.
Creating the Session Table in MySQL:
CREATE TABLE SPRING_SESSION (
PRIMARY_ID CHAR(36) NOT NULL,
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
EXPIRY_TIME BIGINT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
PRIMARY KEY (PRIMARY_ID)
) ENGINE=InnoDB;
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
Creates the
SPRING_SESSION
table to store session data.Indexes to improve performance for searching by
SESSION_ID
,EXPIRY_TIME
, andPRINCIPAL_NAME
.
Reusing Main Configuration Class and Controller:
- The main class and controller from the Redis example can be reused without changes.
Complex Example: Session Management with Spring Security
Project Configuration
Adding Dependencies in pom.xml
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
Spring Security Configuration in SecurityConfig.java
:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.logout().permitAll();
}
}
@EnableWebSecurity
: Enables web security configuration.HttpSecurity
: Configures HTTP security, allowing public access to the root ("/"
) and requiring authentication for all other requests.
Reusing Redis Connection Configuration in application.properties
:
- The Redis configuration is the same as in the simple example.
Reusing Main Configuration Class and Controllers:
- The main class and controllers can be reused, adding security logic as needed.
Real-World Use Case: E-commerce Application with Spring Session and Redis
A distributed e-commerce application needs to keep user sessions consistent across different server instances, ensuring that shopping cart items and authentication states are preserved even if the user is redirected between servers during navigation.
Implementation
Project Configuration:
- Use the configuration from the simple Redis example.
Shopping Cart Controller:
@Controller
@RequestMapping("/cart")
public class CartController {
@GetMapping
public String showCart(HttpSession session, Model model) {
Cart cart = (Cart) session.getAttribute("cart");
if (cart == null) {
cart = new Cart();
session.setAttribute("cart", cart);
}
model.addAttribute("cart", cart);
return "cart";
}
@PostMapping("/add")
public String addItemToCart(@RequestParam("productId") Long productId, HttpSession session) {
Cart cart = (Cart) session.getAttribute("cart");
if (cart == null) {
cart = new Cart();
session.setAttribute("cart", cart);
}
cart.addItem(productId);
return "redirect:/cart";
}
}
Cart Model:
public class Cart {
private List<Long> items = new ArrayList<>();
public void addItem(Long productId) {
items.add(productId);
}
public List<Long> getItems() {
return items;
}
}
Redis Configuration:
- Ensure that Redis is correctly configured and running as per the simple example instructions.
Conclusion
Spring Session simplifies distributed session management in Spring Boot applications, providing seamless integration with various types of persistent storage. By using Redis, JDBC, or Hazelcast, developers can ensure that user sessions are consistent and persistent in distributed environments, improving application scalability and resilience.
Subscribe to my newsletter
Read articles from André Felipe Costa Bento directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
André Felipe Costa Bento
André Felipe Costa Bento
Fullstack Software Engineer.