Session 管理
Spring Boot 默认使用 Servlet 容器(Tomcat)的 HttpSession,Session 存储在 JVM 内存中。多实例部署时各节点 Session 不共享,通常需要引入 Spring Session 将 Session 集中存储到 Redis 或数据库。
默认 HttpSession
无需额外依赖,直接通过 Servlet API 操作:
@RestController
public class LoginController {
@PostMapping("/login")
public String login(@RequestBody LoginRequest req, HttpSession session) {
User user = authService.authenticate(req.username(), req.password());
session.setAttribute("currentUser", user);
session.setMaxInactiveInterval(1800); // 30 分钟不活跃则过期
return "登录成功";
}
@GetMapping("/me")
public User currentUser(HttpSession session) {
User user = (User) session.getAttribute("currentUser");
if (user == null) throw new UnauthorizedException("未登录");
return user;
}
@PostMapping("/logout")
public String logout(HttpSession session) {
session.invalidate(); // 销毁 Session
return "已退出";
}
}配置
server:
servlet:
session:
timeout: 30m # Session 过期时间
cookie:
name: JSESSIONID
http-only: true # 禁止 JS 读取,防 XSS
secure: true # 仅 HTTPS 传输
same-site: lax # 防 CSRF
path: /Spring Session(分布式 Session)
Spring Session 透明替换 HttpSession,将数据写入 Redis / JDBC / Hazelcast 等,业务代码无需修改。
依赖(以 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>配置
spring:
session:
store-type: redis # redis / jdbc / hazelcast / none
timeout: 30m
redis:
namespace: "spring:session" # Redis key 前缀
flush-mode: on-save # on-save(默认)或 immediate
cleanup-cron: "0 * * * * *" # 过期 key 清理任务(cron 表达式)
data:
redis:
host: localhost
port: 6379开启 Session(Spring Boot 自动配置已包含,显式声明可指定存储类型):
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class SessionConfig {
// 可在此自定义 RedisSerializer
}Redis 中存储结构:
spring:session:sessions:{sessionId} ← Hash,存储 Session 属性
spring:session:sessions:expires:{sessionId} ← String,用于 TTL 过期
spring:session:expirations:{分钟时间戳} ← Set,支持定期清理
自定义 Session 序列化
默认使用 JDK 序列化,推荐改为 JSON:
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}存储在 Session 中的对象需要有无参构造函数,以支持 JSON 反序列化。
与 Spring Security 集成
Spring Security 自动与 Spring Session 协作,无需额外配置。Security 的 SecurityContext 会被序列化到 Session:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/dashboard", true)
)
.logout(logout -> logout
.logoutUrl("/logout")
.invalidateHttpSession(true) // 退出时销毁 Session
.deleteCookies("JSESSIONID")
)
// 防止 Session 固定攻击
.sessionManagement(sm -> sm
.sessionFixation().migrateSession() // 登录后更换 Session ID
.maximumSessions(1) // 同一账号最多 1 个会话
.maxSessionsPreventsLogin(false) // false:新登录踢出旧会话
);
return http.build();
}
}并发 Session 控制
限制单账号同时在线数量,需注册 HttpSessionEventPublisher:
// 必须注册,Spring Security 才能感知 Session 创建/销毁事件
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}// Security 配置中
.sessionManagement(sm -> sm
.maximumSessions(1)
.maxSessionsPreventsLogin(true) // true:超出则拒绝新登录
.expiredUrl("/login?expired=1") // 被踢出后跳转地址
)Session 事件监听
监听 Session 的创建、销毁和属性变更:
@Component
public class SessionEventListener
implements HttpSessionListener, HttpSessionAttributeListener {
@Override
public void sessionCreated(HttpSessionEvent event) {
log.info("Session 创建: {}", event.getSession().getId());
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
log.info("Session 销毁: {}", event.getSession().getId());
// 可在此清理与该 Session 关联的资源
}
@Override
public void attributeAdded(HttpSessionBindingEvent event) {
log.debug("Session 属性写入: {}", event.getName());
}
}手动操作 Session(Spring Session API)
通过 SessionRepository 跨请求直接操作 Session:
@Service
public class SessionAdminService {
private final FindByIndexNameSessionRepository<? extends Session> sessionRepository;
// 查找某用户的所有 Session(需配合 PrincipalNameIndexer)
public Map<String, ? extends Session> getUserSessions(String username) {
return sessionRepository.findByPrincipalName(username);
}
// 强制下线某用户的所有 Session
public void forceLogout(String username) {
sessionRepository.findByPrincipalName(username)
.forEach((id, session) -> sessionRepository.deleteById(id));
}
}JDBC 存储(无 Redis 场景)
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
</dependency>spring:
session:
store-type: jdbc
jdbc:
initialize-schema: always # 自动建表(生产环境建议 never,手动执行 SQL)
table-name: SPRING_SESSIONSpring Session 会自动创建 SPRING_SESSION 和 SPRING_SESSION_ATTRIBUTES 两张表。
Session vs Token 对比
| 维度 | Session(服务端状态) | JWT Token(客户端状态) |
|---|---|---|
| 存储位置 | 服务端(内存 / Redis) | 客户端(Cookie / Header) |
| 吊销能力 | 立即生效(删除 Session) | 复杂(需黑名单) |
| 水平扩展 | 需共享存储(Redis) | 天然无状态 |
| 数据大小 | Session ID 极小 | Token 随 Payload 增大 |
| 适用场景 | Web 应用、传统表单登录 | 前后端分离、移动端、微服务 |
Token 方案详见 OAuth2与JWT。
相关链接
- Redis集成 — Spring Session Redis 后端配置
- OAuth2与JWT — 无状态 Token 认证方案
- 安全 — Spring Security 整体配置
- 跨域处理 — 跨域场景下 Session Cookie 的 SameSite 配置