Spring Boot 接入 Redis Session 实战
- 发布时间:2026-06-16 10:06:50
- 本文热度:浏览 11 赞 0 评论 0
- 文章标签: Spring Boot Spring Session Redis
- 全文共1字,阅读约需1分钟
为什么要把 Session 放到 Redis
单机应用里,HttpSession 默认存在应用进程内。用户登录后,请求只要一直打到这台机器,基本没什么问题。
问题从多实例部署开始:
- 用户第一次请求落到 A 实例,Session 存在 A 的内存里;
- 下一次请求被负载均衡打到 B 实例;
- B 实例找不到 Session,于是用户像是“突然掉线”了。
当然,可以用 Nginx 的 ip_hash 或者负载均衡的粘性会话把同一个用户固定到一台机器上。但这只是绕开问题,不是解决问题。实例扩缩容、机器重启、灰度发布时,Session 仍然会变成一个麻烦点。
把 Session 交给 Redis,本质上是把“用户状态”从应用进程里拆出去。应用实例变成无状态,任意一台机器都可以处理同一个用户的请求。
这才是 Spring Boot 接入 Redis Session 最主要的价值。
Spring Session 做了什么
Spring Boot 接 Redis Session 通常不是自己手写一套 HttpSession 读写 Redis 的逻辑,而是使用 Spring Session。
Spring Session 会在 Servlet Filter 层接管原生 HttpSession,把 Session 的创建、读取、更新、过期交给 Redis 存储。业务代码里依然可以继续这样写:
request.getSession().setAttribute("userId", userId);
或者:
@GetMapping("/me")
public Object me(HttpSession session) {
return session.getAttribute("userId");
}
对业务代码来说,它仍然像是在使用普通 HttpSession。差别在底层:数据不再放在当前 JVM 内存里,而是写到了 Redis。
Spring Boot 对 Spring Session 有自动配置支持。Servlet Web 应用使用 Redis 作为 Session 存储时,可以通过 spring-boot-starter-session-data-redis 自动配置;官方文档也明确说明,Servlet 场景下自动配置会替代手动使用 @Enable*HttpSession 的需求。([Home][1])
引入依赖
以 Spring Boot 3.x 为例,Maven 可以这样引入:
<dependencies>
<!-- Web 应用基础依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Redis 客户端与 Spring Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Spring Session Redis 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-session-data-redis</artifactId>
</dependency>
</dependencies>
如果项目没有使用 Spring Boot starter,也可以直接引入:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
不过在 Spring Boot 项目里,优先使用 starter 更省心,版本也由 Spring Boot 统一管理。
配置 Redis 和 Session
常见的 application.yml 配置如下:
spring:
data:
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 3000ms
session:
store-type: redis
timeout: 30m
redis:
namespace: spring:session:demo
flush-mode: on_save
repository-type: default
server:
servlet:
session:
cookie:
name: DEMO_SESSION
http-only: true
secure: false
same-site: lax
几个配置值得单独说一下。
spring.session.timeout
这是 Session 的过期时间。比如:
spring:
session:
timeout: 30m
表示用户 30 分钟没有访问,Session 会过期。
Spring Boot 官方文档说明,Session 超时时间可以使用 spring.session.timeout 配置;如果 Servlet Web 应用没有设置这个属性,会回退使用 server.servlet.session.timeout。([Home][1])
spring.session.redis.namespace
建议每个应用都显式配置 namespace:
spring:
session:
redis:
namespace: spring:session:order-service
默认 namespace 是 spring:session。如果多个应用共用一个 Redis,又都使用默认 namespace,后期排查会很难受。轻则 key 混在一起不好看,重则不同应用之间的 Session 结构互相影响。
Spring Session 官方文档也建议在多个应用使用同一个 Redis 实例时,通过 namespace 隔离 Session key。([Home][2])
flush-mode
spring:
session:
redis:
flush-mode: on_save
常见值有:
on_save:请求结束时统一保存 Session;immediate:Session 一发生变更就立即写入 Redis。
大部分业务用 on_save 就够了。immediate 更及时,但 Redis 写入次数也会更多。除非你明确知道自己需要这个语义,否则不要一上来就改成 immediate。
写一个简单的登录示例
先写一个登录接口,把用户信息放进 Session:
@RestController
@RequestMapping("/api")
public class LoginController {
@PostMapping("/login")
public Map<String, Object> login(HttpSession session,
@RequestParam String username) {
session.setAttribute("loginUser", username);
return Map.of(
"success", true,
"sessionId", session.getId(),
"username", username
);
}
@GetMapping("/profile")
public Map<String, Object> profile(HttpSession session) {
Object loginUser = session.getAttribute("loginUser");
if (loginUser == null) {
return Map.of(
"loggedIn", false,
"message", "未登录"
);
}
return Map.of(
"loggedIn", true,
"username", loginUser,
"sessionId", session.getId()
);
}
@PostMapping("/logout")
public Map<String, Object> logout(HttpSession session) {
session.invalidate();
return Map.of(
"success", true,
"message", "已退出登录"
);
}
}
启动 Redis 和 Spring Boot 应用后,调用登录接口:
curl -i -X POST "http://localhost:8080/api/login?username=zhangsan"
响应头里会看到类似:
Set-Cookie: DEMO_SESSION=xxxxxx; Path=/; HttpOnly; SameSite=Lax
再带着 Cookie 访问:
curl -i "http://localhost:8080/api/profile" \
-H "Cookie: DEMO_SESSION=xxxxxx"
如果能正常返回用户信息,说明 Spring Session 已经接管了 Session。
此时可以在 Redis 里看一下 key:
redis-cli keys "spring:session:demo*"
一般会看到类似这样的结构:
spring:session:demo:sessions:xxxxxx
spring:session:demo:sessions:expires:xxxxxx
spring:session:demo:expirations:171xxxxxxx
实际 key 结构会随 Spring Session 版本和 repository 类型有所差异,不建议业务代码依赖这些内部 key。排查问题时可以看,业务逻辑里不要直接操作它。
JSON 序列化:一个很容易被忽略的坑
Spring Session 默认会序列化 Session attribute。很多项目一开始直接把用户对象塞进 Session:
session.setAttribute("loginUser", user);
短期看没问题,后面容易踩坑:
- 类结构变了,旧 Session 反序列化失败;
- 多个服务共享 Session,但实体类版本不一致;
- Session 里放了不该序列化的对象;
- Redis 里的值不可读,排查不方便。
如果 Session 里只是保存用户 ID、用户名、权限摘要,问题会少很多。
推荐做法是:Session 里放小对象、稳定字段,不要把完整 UserEntity、ORM 懒加载对象、复杂上下文一股脑塞进去。
如果确实要调整序列化方式,可以注册名为 springSessionDefaultRedisSerializer 的 Bean。Spring Session 官方文档说明,该 Bean 名称用于覆盖默认 Redis Session 序列化器,并给出了使用 JSON 序列化的配置方式。([Home][2])
示例:
@Configuration
public class SessionConfig {
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer(ObjectMapper objectMapper) {
return new GenericJackson2JsonRedisSerializer(objectMapper);
}
}
但这里不要误会:换成 JSON 不是万能药。它只是让数据更可读、跨版本时更可控。真正重要的是别把 Session 当成垃圾桶。
default 和 indexed 怎么选
Spring Session Redis 有两类常见 repository:
spring:
session:
redis:
repository-type: default
以及:
spring:
session:
redis:
repository-type: indexed
简单判断:
- 普通登录态保存,用
default; - 需要按用户查找所有 Session,比如“查看当前账号所有登录设备”“踢掉某个用户的全部 Session”,考虑
indexed。
Spring Session 官方文档说明,RedisSessionRepository 是基础实现,不做额外索引;RedisIndexedSessionRepository 支持索引能力,可以根据用户等条件查找 Session。([Home][2])
如果使用 indexed,可以通过 FindByIndexNameSessionRepository 查某个用户的 Session:
@Service
public class UserSessionService {
private final FindByIndexNameSessionRepository<? extends Session> sessionRepository;
public UserSessionService(FindByIndexNameSessionRepository<? extends Session> sessionRepository) {
this.sessionRepository = sessionRepository;
}
public Collection<? extends Session> findUserSessions(String username) {
return sessionRepository.findByPrincipalName(username).values();
}
public void deleteSession(String sessionId) {
sessionRepository.deleteById(sessionId);
}
}
不过 indexed 不是白来的。它会维护额外索引,数据结构更复杂。官方文档也提醒,RedisIndexedSessionRepository 在 Redis Cluster 场景下订阅事件时存在限制,可能导致某些索引无法及时清理。([Home][2])
所以不要为了“看起来更强”就开 indexed。用不到按用户查 Session,就别加这层复杂度。
Cookie 配置不能随便抄
Session 存 Redis,不代表 Cookie 就不重要了。
浏览器和服务端之间仍然靠 Cookie 里的 Session ID 关联用户。Redis 只是存了 Session 数据,Cookie 丢了、跨域没带上、SameSite 配错,服务端一样找不到用户状态。
常见配置:
server:
servlet:
session:
cookie:
name: DEMO_SESSION
http-only: true
secure: true
same-site: none
几个点要注意:
前后端同域
如果前端和后端同域,比如:
https://example.com
https://example.com/api
通常用:
same-site: lax
secure: true
就比较合适。
前后端跨域
如果前端是:
https://www.example.com
后端是:
https://api.example.com
或者完全不同域名,就要同时处理:
- 后端 CORS 允许携带凭证;
- 前端请求开启
credentials; - Cookie 设置
SameSite=None; Secure; - HTTPS 环境。
前端示例:
fetch("https://api.example.com/api/profile", {
method: "GET",
credentials: "include"
});
后端 CORS 示例:
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://www.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowCredentials(true);
}
};
}
}
这里最常见的错误是:后端 Session 配好了,Redis 也有数据,但浏览器请求根本没把 Cookie 带回来。然后开发者开始怀疑 Spring Session,其实问题在 Cookie 和跨域。
生产环境还要看这些问题
1. Redis 不能裸奔
Session 是登录态。Redis 出问题,用户登录态就会大面积异常。
生产环境至少要考虑:
- Redis 主从或集群;
- 连接池配置;
- 密码和网络隔离;
- 慢查询和内存监控;
- key 淘汰策略;
- Redis 故障时的降级预期。
尤其是 maxmemory-policy,不要随便使用会淘汰 Session key 的策略。Redis 内存打满后,如果 Session 被淘汰,表现出来就是用户随机掉线。
2. Session 不要存太大
Redis 很快,但不是让你把所有用户上下文都塞进去。
Session 里建议放:
userId
username
roleCodes
tenantId
loginTime
不建议放:
完整用户实体
菜单树
权限大对象
购物车大列表
上传文件临时内容
第三方接口返回的大 JSON
Session 越大,请求结束时写 Redis 的成本越高,网络传输和序列化开销也越明显。
很多 Session 性能问题,不是 Redis 慢,是你放进去的东西太重。
3. 不要混用 Token 和 Session 逻辑
有些项目一边接 Redis Session,一边又自己发 JWT 或自定义 Token。结果登录态有两套来源:
- Cookie 里有 Session ID;
- Header 里有 Authorization;
- Redis 里又存用户状态。
这不是不行,但要设计清楚。否则排查登录问题时会很混乱:到底是 Session 过期、Token 过期,还是 Redis 里的用户状态被删了?
如果是传统后台管理系统,Redis Session 很合适。
如果是开放 API、移动端、多端认证,可能 Token 体系更自然。不要因为 Redis Session 配起来简单,就把它硬套到所有认证场景。
一个推荐的最小配置
普通后台系统可以从这个配置开始:
spring:
data:
redis:
host: redis.example.com
port: 6379
password: your-password
database: 0
timeout: 3000ms
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 2
max-wait: 1000ms
session:
store-type: redis
timeout: 30m
redis:
namespace: spring:session:admin
flush-mode: on_save
repository-type: default
server:
servlet:
session:
cookie:
name: ADMIN_SESSION
http-only: true
secure: true
same-site: lax
再配一个序列化器:
@Configuration
public class SessionRedisConfig {
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer(ObjectMapper objectMapper) {
return new GenericJackson2JsonRedisSerializer(objectMapper);
}
}
业务代码里只存轻量字段:
public record LoginUserSession(
Long userId,
String username,
List<String> roles
) implements Serializable {
}
登录时:
session.setAttribute("loginUser", new LoginUserSession(
user.getId(),
user.getUsername(),
roleCodes
));
读取时:
LoginUserSession loginUser =
(LoginUserSession) session.getAttribute("loginUser");
这套方案不花哨,但足够稳。真正上线后,稳定性往往比“功能全”更重要。
小结
Spring Boot 接入 Redis Session 的核心并不复杂:引入 Spring Session Redis 依赖,配置 Redis,设置 Session 超时时间和 namespace,业务代码继续使用 HttpSession。
真正需要认真处理的是后面的工程细节:
- Session 放 Redis 是为了解决多实例共享登录态;
- namespace 必须按应用隔离;
- Session attribute 不要塞大对象;
- JSON 序列化可以提升可读性和兼容性,但不能替代良好的 Session 设计;
- 普通场景用
defaultrepository,需要按用户查 Session 再考虑indexed; - 跨域场景要重点检查 Cookie、CORS、SameSite 和 HTTPS;
- Redis 是登录态基础设施,生产环境必须按核心组件对待。
Spring Session 把接入门槛降得很低,但它没有替你设计登录态边界。边界不清楚,Redis 只是把原来藏在 JVM 里的问题搬到了另一处。
参考资料
- Spring Boot Reference Documentation:Spring Session 自动配置、Redis Session starter、Session timeout 配置。([Home][1])
- Spring Session Reference Documentation:Redis namespace、JSON 序列化、repository type、indexed repository 说明。([Home][2])