- 什么是 Session?
- 为什么需要 Session?
- Session 的工作原理
- 在 Java 中如何管理 Session(Servlet/JSP 和 Spring Boot)
- Session 生命周期
- Session 的常见问题与最佳实践
什么是 Session?
Session(会话) 是一种在 Web 服务器端用来跟踪用户状态的机制。

你可以把它想象成一个“用户专属的临时小储物柜”,当一个用户访问你的网站时,服务器会为他创建一个独一无二的 Session 对象,并分配一个唯一的 ID(通常称为 Session ID 或 JSESSIONID),这个 ID 会被发送给用户的浏览器,浏览器在后续的每次请求中都会带上这个 ID,服务器通过这个 ID 就能找到对应的 Session,从而识别出是哪个用户,并可以在这个 Session 中存储和读取该用户的信息。
Session 中通常存储什么?
- 用户登录信息(用户名、ID 等)
- 购物车数据
- 用户权限信息
- 用户的个性化设置
为什么需要 Session?
HTTP 协议本身是无状态的,这意味着服务器不会记录任何关于之前请求的信息,每次请求都是独立的、全新的,这带来了一个问题:
如何在一系列连续的请求中保持用户的状态?
(图片来源网络,侵删)
用户登录后,访问个人中心页面,服务器如何知道“这个请求是刚才那个登录用户发来的,而不是一个未登录的陌生人”?
Session 就是为了解决这个问题而生的。 它通过在服务器端建立与特定用户的关联,实现了“有状态”的交互体验。
Session 的工作原理
Session 的工作流程可以概括为以下几个步骤:
-
创建/获取 Session:
(图片来源网络,侵删)- 当一个用户第一次访问网站,并且服务器端代码中调用了获取 Session 的方法(
request.getSession())时,服务器会为这个用户创建一个新的HttpSession对象。 - 如果用户不是第一次访问,并且在浏览器中携带了有效的
Session ID,服务器则会根据这个 ID 找到并返回已存在的HttpSession对象。
- 当一个用户第一次访问网站,并且服务器端代码中调用了获取 Session 的方法(
-
分配 Session ID:
- 服务器为新创建的 Session 生成一个唯一的、随机的
Session ID(A1B2C3D4E5F6G7H8)。
- 服务器为新创建的 Session 生成一个唯一的、随机的
-
Session ID 的传递:
- 服务器会将这个
Session ID通过 Cookie 的形式发送给用户的浏览器,这个 Cookie 通常有一个名称,如JSESSIONID。 - 从这时起,只要浏览器不关闭且 Cookie 未过期,用户在同一个网站的每一次后续请求,浏览器都会自动在 HTTP 请求头中携带这个
JSESSIONIDCookie。
- 服务器会将这个
-
服务器识别与处理:
- 服务器接收到后续请求后,会检查请求头中的
JSESSIONID。 - 服务器根据这个 ID 在其内存(或专门的存储区域)中查找对应的
HttpSession对象。 - 找到后,服务器就可以对这个 Session 进行读写操作,从而实现用户状态的维护。
- 服务器接收到后续请求后,会检查请求头中的
图示流程:
[浏览器] <--(请求1, 无Session ID)--> [服务器]
[服务器] --(创建新Session, 生成ID: A1B2...)--> [服务器]
[服务器] --(响应2, Set-Cookie: JSESSIONID=A1B2...)--> [浏览器]
[浏览器] --(请求3, Cookie: JSESSIONID=A1B2...)--> [服务器]
[服务器] --(根据ID找到Session, 读取/写入数据)--> [服务器]
[服务器] --(响应4, 数据)--> [浏览器]
在 Java 中如何管理 Session
A. 在原生 Servlet/JSP 中
这是最基础、最直接的 Session 管理方式。
获取 Session 对象
// 获取当前用户的 Session,如果不存在,则创建一个新的。 HttpSession session = request.getSession(); // 如果只想获取已存在的 Session,不想创建新的,可以使用 // HttpSession session = request.getSession(false); // Session 不存在,返回 null。
向 Session 中存取数据
HttpSession 对象像一个 Map,可以存取各种类型的对象。
// 存储数据
// 参数1: 键 (String)
// 参数2: 值 (Object)
session.setAttribute("username", "张三");
session.setAttribute("userRole", "admin");
session.setAttribute("shoppingCart", new ArrayList<>());
// 获取数据
String username = (String) session.getAttribute("username");
String userRole = (String) session.getAttribute("userRole");
List<String> cart = (List<String>) session.getAttribute("shoppingCart");
// 如果键不存在,getAttribute() 返回 null
if (username == null) {
// 用户未登录或已过期
}
// 移除数据
session.removeAttribute("userRole");
获取 Session ID 和基本信息
// 获取 Session ID
String sessionId = session.getId();
System.out.println("Session ID: " + sessionId);
// 获取 Session 创建时间
long creationTime = session.getCreationTime();
System.out.println("创建时间: " + new Date(creationTime));
// 获取最后一次访问时间
long lastAccessedTime = session.getLastAccessedTime();
System.out.println("最后访问时间: " + new Date(lastAccessedTime));
// 设置 Session 的最大不活动时间(单位:秒)
// 1800秒 = 30分钟
session.setMaxInactiveInterval(1800);
B. 在 Spring Boot 中
Spring Boot 对 Session 管理进行了封装,使其更加便捷和强大。
启用 Session 支持
在 Spring Boot 中,默认就是启用的,你不需要额外配置,如果你需要自定义,可以在配置文件(application.properties 或 application.yml)中设置:
# 设置 Session 超时时间,单位为秒 (默认 30 分钟) server.servlet.session.timeout=1800 # 设置 Session Cookie 的名称 (默认为 JSESSIONID) server.servlet.session.cookie.name=MYSESSIONID # 设置 Session Cookie 的路径 server.servlet.session.cookie.path=/myapp # 设置 Session 是否强制使用 Cookie (默认为 true) server.servlet.session.cookie.http-only=true # 如果设置为 true,则当浏览器关闭时,Session 失效 (默认为 false) # server.servlet.session.cookie.secure=true
在 Controller 中使用 Session Spring 提供了两种主要方式来操作 Session:
通过 HttpSession 参数注入
这种方式和 Servlet 原生 API 几乎一样,非常直观。
import jakarta.servlet.http.HttpSession; // 注意是 jakarta 包
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/login")
public String login(@RequestParam String username, HttpSession session) {
// 将用户名存入 Session
session.setAttribute("username", username);
return "登录成功,欢迎 " + username;
}
@GetMapping("/profile")
public String getProfile(HttpSession session) {
String username = (String) session.getAttribute("username");
if (username == null) {
return "请先登录";
}
return "用户: " + username;
}
@PostMapping("/logout")
public String logout(HttpSession session) {
// 使整个 Session 失效,并移除所有数据
session.invalidate();
return "已退出登录";
}
}
通过 @SessionAttribute 注解
这个注解用于将已经存在于 Session 中的数据直接绑定到 Controller 方法的参数上,它主要用于读取数据。
import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/cart")
public class CartController {
@GetMapping("/view")
public String viewCart(@SessionAttribute("username") String username) {
// 直接从 Session 中获取 username,如果不存在会抛出异常
// 适用于需要用户必须登录才能访问的场景
return "用户 " + username + " 的购物车";
}
// 如果数据可能不存在,可以设置 required = false
@GetMapping("/optional-view")
public String optionalView(@SessionAttribute(value = "username", required = false) String username) {
if (username == null) {
return "游客访问";
}
return "用户 " + username + " 的购物车";
}
}
通过 @SessionAttributes 注解 (类级别)
这个注解用在 Controller 类上,用于声明哪些模型属性需要被存入 Session,它主要用于存储数据,通常与 @ModelAttribute 配合使用。
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
// 声明 model 中名为 "user" 的属性需要存入 Session
@SessionAttributes("user")
@RestController
@RequestMapping("/profile")
public class ProfileController {
@GetMapping("/edit")
public String editProfile(Model model) {
// 假设从数据库获取用户信息
User user = new User("李四", "lisi@example.com");
// 将 user 对象添加到 Model 中
// 因为有 @SessionAttributes("user"),所以这个 user 会被自动存入 Session
model.addAttribute("user", user);
return "Profile edited and saved to session.";
}
@GetMapping("/show")
public String showProfile(@ModelAttribute("user") User user) {
// 直接从 Session 中获取 user 对象
return "User Info: " + user.getName() + ", " + user.getEmail();
}
}
Session 生命周期
- 创建:当调用
request.getSession()时创建。 - 活跃:用户持续发送请求,并且请求间隔小于
setMaxInactiveInterval()设置的时间。 - 过期:当用户停止活动的时间超过了设定的最大不活动时间,Session 会被服务器标记为过期,并可能被垃圾回收器移除,在 Spring Boot 中,可以通过
server.servlet.session.timeout配置。 - 销毁:
- 被动销毁:调用
session.invalidate()方法,立即销毁当前 Session。 - 主动销毁:服务器(如 Tomcat)检测到 Session 过期后,会自动将其移除。
- 应用关闭:当 Web 应用停止或服务器关闭时,所有 Session 都会被销毁。
- 被动销毁:调用
Session 的常见问题与最佳实践
常见问题
-
Session 固定攻击
- 问题:攻击者先获取一个有效的 Session ID,然后通过某种方式(如 XSS 漏洞)让受害者使用这个 ID 登录,一旦受害者登录,攻击者就拥有了和受害者相同的会话权限。
- 解决:在用户成功登录后,重新生成一个新的 Session ID,并将旧 Session 中的数据复制到新 Session 中,然后销毁旧 Session,这可以切断攻击者与有效 Session ID 的关联。
-
Session 劫持
- 问题:攻击者窃取了合法用户的 Session ID,然后冒充该用户。
- 解决:
- 绑定 IP 地址:将 Session ID 与客户端 IP 地址绑定,IP 发生变化,则 Session 失效,但这种方法不友好,因为用户可能会更换 IP(如从 Wi-Fi 切换到 4G)。
- 绑定 User-Agent:将 Session ID 与浏览器的 User-Agent 字符串绑定。
- 定期更换 Session ID。
- 使用 HTTPS,防止 Session ID 在传输过程中被窃听。
-
Session 溢出
- 问题:在 Session 中存储了过多或过大的对象,导致服务器内存耗尽。
- 解决:只将必要且轻量的数据(如用户 ID)存入 Session,而将如购物车、用户详情等大对象存储在数据库或缓存(如 Redis)中,Session 中只存储其引用 ID。
-
分布式环境下的 Session 共享
- 问题:在负载均衡或微服务架构下,用户的请求可能被分发到不同的服务器,Session 存储在单台服务器的内存中,那么下一次请求可能就找不到 Session 了。
- 解决:
- 使用共享存储:将 Session 数据集中存储在所有服务器都能访问的地方,如 Redis、Memcached 或 数据库,这是最常用和推荐的方案,Spring Session 提供了与 Redis 等存储的完美集成。
- 粘性会话:配置负载均衡器,确保来自同一用户的请求总是被发送到同一台服务器,但这会失去负载均衡的优势,并成为单点故障的隐患。
最佳实践
- 只存必要信息:Session 是宝贵的服务器资源,不要将大量或非序列化的对象存入其中,最佳实践是只存一个用户标识符(如
userId),然后通过这个 ID 去数据库或缓存中查找完整信息。 - 设置合理的超时时间:根据业务需求设置
session.setMaxInactiveInterval(),避免 Session 长期占用服务器资源。 - 使用 HTTPS:始终使用 HTTPS 协议,保护 Session ID 在网络传输中的安全。
- 及时清理:用户退出登录时,务必调用
session.invalidate()来销毁会话。 - 考虑分布式方案:在设计之初就应考虑系统是否可能扩展为分布式架构,并优先选择支持 Session 共享的方案(如 Spring Session + Redis)。
- 注意线程安全:Session 对象本身是线程安全的,但如果你在 Session 中存储了自定义对象,并且这些对象会被多个线程同时修改,那么你需要确保这些对象是线程安全的。

