SpringSession会话管理之Redis与JDBC存储实现方式
作者:程序媛学姐
引言
在分布式应用架构中,会话管理是一个关键挑战。传统的容器内存储会话的方法在多个服务实例间难以共享,导致用户在不同服务器间切换时会话丢失。
Spring Session提供了一种创建和管理用户会话的标准方法,使会话可以脱离应用服务器的限制,实现跨多个服务实例的会话共享。
一、Spring Session基本概念
Spring Session是Spring生态系统中的一个项目,它提供了一套API和实现来管理用户会话。其核心优势在于能够将会话存储从应用服务器剥离,放置到外部存储系统中,从而实现会话的跨应用共享和持久化。
Spring Session的主要特性包括:
- 透明集成:对现有应用代码几乎无侵入性
- 多种存储方式:支持Redis、JDBC、MongoDB等多种后端存储
- 安全支持:与Spring Security无缝集成
- REST API支持:提供了基于请求头的会话传递机制,适用于RESTful应用
- WebSocket支持:保证WebSocket通信中会话的连续性
要在项目中使用Spring Session,首先需要添加相应的依赖。以Spring Boot项目为例,根据存储类型选择相应的依赖:
<!-- Redis存储 -->
<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>
<!-- 或者JDBC存储 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>二、Redis实现会话存储
Redis是一个高性能的键值存储系统,非常适合用于存储会话数据。
使用Redis存储会话具有低延迟、高可用性和水平扩展能力,是分布式系统中会话管理的理想选择。
2.1 配置Redis会话存储
在Spring Boot应用中,配置Redis会话存储非常简单:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800) // 会话过期时间:30分钟
public class RedisSessionConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
return template;
}
}@EnableRedisHttpSession注解自动配置了一个RedisIndexedSessionRepository,它负责会话的创建、保存、删除和过期处理。
在application.properties或application.yml中配置Redis连接信息:
# Redis服务器连接配置 spring.redis.host=localhost spring.redis.port=6379 # 如果需要密码认证 spring.redis.password=your-password # Spring Session配置 spring.session.store-type=redis spring.session.redis.namespace=spring:session
2.2 Redis会话示例
Redis会话存储的工作机制是透明的,开发者可以像使用普通HttpSession一样使用它:
import javax.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SessionController {
@GetMapping("/session/set")
public String setSessionAttribute(HttpSession session) {
session.setAttribute("username", "john.doe");
session.setAttribute("role", "admin");
return "Session attributes set successfully";
}
@GetMapping("/session/get")
public String getSessionAttribute(HttpSession session) {
String username = (String) session.getAttribute("username");
String role = (String) session.getAttribute("role");
return "Username: " + username + ", Role: " + role;
}
}在后台,Spring Session拦截了HttpSession的操作,将会话数据存储在Redis中。Redis存储会话的数据结构如下:
spring:session:sessions:<sessionId>- 存储会话本身spring:session:sessions:expires:<sessionId>- 存储会话的过期信息spring:session:expirations:<expireTime>- 按过期时间索引的会话列表
三、JDBC实现会话存储
对于已经使用关系型数据库的应用,JDBC会话存储是一个不错的选择。它利用现有的数据库基础设施,简化了系统架构和运维复杂度。
3.1 配置JDBC会话存储
在Spring Boot应用中,配置JDBC会话存储同样简单:
import org.springframework.context.annotation.Configuration;
import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession;
@Configuration
@EnableJdbcHttpSession(maxInactiveIntervalInSeconds = 1800, tableName = "SPRING_SESSION")
public class JdbcSessionConfig {
// JDBC会话存储不需要额外的Bean定义
}@EnableJdbcHttpSession注解配置了一个JdbcIndexedSessionRepository,它使用JDBC来存储和检索会话数据。
在application.properties中配置数据源和会话存储类型:
# 数据源配置 spring.datasource.url=jdbc:mysql://localhost:3306/session_db spring.datasource.username=root spring.datasource.password=password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # Spring Session配置 spring.session.store-type=jdbc spring.session.jdbc.initialize-schema=always
Spring Session会自动创建所需的数据库表。默认情况下,会创建以下表:
SPRING_SESSION- 存储会话元数据SPRING_SESSION_ATTRIBUTES- 存储会话属性
3.2 JDBC会话表结构
JDBC会话存储使用的表结构如下:
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), CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID) ); CREATE TABLE SPRING_SESSION_ATTRIBUTES ( SESSION_PRIMARY_ID CHAR(36) NOT NULL, ATTRIBUTE_NAME VARCHAR(200) NOT NULL, ATTRIBUTE_BYTES BLOB NOT NULL, CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME), CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE );
这些表存储了会话ID、创建时间、最后访问时间、过期时间以及所有会话属性。
四、高级特性与最佳实践
4.1 自定义会话序列化
默认情况下,Spring Session使用JDK序列化来存储会话属性。为了提高性能和兼容性,可以自定义序列化机制:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@Configuration
@EnableRedisHttpSession
public class RedisSessionConfig {
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
}对于JDBC存储,可以实现SessionConverter接口来自定义序列化:
@Configuration
@EnableJdbcHttpSession
public class JdbcSessionConfig {
@Bean
public SessionConverter sessionConverter() {
return new JacksonSessionConverter();
}
}4.2 会话事件监听
Spring Session提供了一系列事件,允许开发者在会话生命周期的不同阶段执行自定义逻辑:
import org.springframework.context.event.EventListener;
import org.springframework.session.events.*;
import org.springframework.stereotype.Component;
@Component
public class SessionEventListener {
@EventListener
public void onCreation(SessionCreatedEvent event) {
System.out.println("Session created: " + event.getSessionId());
}
@EventListener
public void onExpiration(SessionExpiredEvent event) {
System.out.println("Session expired: " + event.getSessionId());
}
@EventListener
public void onDeletion(SessionDeletedEvent event) {
System.out.println("Session deleted: " + event.getSessionId());
}
}4.3 多浏览器会话支持
Spring Session支持在同一用户的不同浏览器或设备上维护独立的会话:
import org.springframework.context.annotation.Bean;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
@Configuration
public class SessionConfig {
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("SESSION");
serializer.setCookiePath("/");
serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
return serializer;
}
}五、性能与安全考虑
在生产环境中使用Spring Session时,需要考虑以下性能和安全因素:
- Redis持久化:配置Redis的持久化策略(AOF或RDB),确保在Redis服务重启时不会丢失会话数据。
- 连接池管理:为Redis或数据库配置适当的连接池,避免连接资源耗尽。
- 会话清理:定期清理过期的会话数据,特别是使用JDBC存储时。
- 会话超时:根据应用安全需求设置合理的会话超时时间。
- Cookie安全:配置会话Cookie的安全属性:
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setUseSecureCookie(true); // 仅在HTTPS连接中传输
serializer.setUseHttpOnlyCookie(true); // 防止JavaScript访问
serializer.setSameSite("Strict"); // 防止CSRF攻击
return serializer;
}总结
Spring Session为分布式应用提供了一个强大而灵活的会话管理解决方案。通过将会话数据从应用服务器转移到Redis或关系型数据库等外部存储中,实现了会话的跨服务实例共享,为构建可扩展的分布式系统奠定了基础。
Redis存储方案提供了高性能和高可用性,适合对响应时间要求较高的应用;而JDBC存储方案则充分利用了现有的数据库基础设施,适合已经大量使用关系型数据库的项目。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
