杰瑞科技汇

Java session 管理有哪些常见方案?

  1. 什么是 Session?
  2. 为什么需要 Session?
  3. Session 的工作原理
  4. 在 Java 中如何管理 Session(Servlet/JSP 和 Spring Boot)
  5. Session 生命周期
  6. Session 的常见问题与最佳实践

什么是 Session?

Session(会话) 是一种在 Web 服务器端用来跟踪用户状态的机制。

Java session 管理有哪些常见方案?-图1
(图片来源网络,侵删)

你可以把它想象成一个“用户专属的临时小储物柜”,当一个用户访问你的网站时,服务器会为他创建一个独一无二的 Session 对象,并分配一个唯一的 ID(通常称为 Session IDJSESSIONID),这个 ID 会被发送给用户的浏览器,浏览器在后续的每次请求中都会带上这个 ID,服务器通过这个 ID 就能找到对应的 Session,从而识别出是哪个用户,并可以在这个 Session 中存储和读取该用户的信息。

Session 中通常存储什么?

  • 用户登录信息(用户名、ID 等)
  • 购物车数据
  • 用户权限信息
  • 用户的个性化设置

为什么需要 Session?

HTTP 协议本身是无状态的,这意味着服务器不会记录任何关于之前请求的信息,每次请求都是独立的、全新的,这带来了一个问题:

如何在一系列连续的请求中保持用户的状态?

Java session 管理有哪些常见方案?-图2
(图片来源网络,侵删)

用户登录后,访问个人中心页面,服务器如何知道“这个请求是刚才那个登录用户发来的,而不是一个未登录的陌生人”?

Session 就是为了解决这个问题而生的。 它通过在服务器端建立与特定用户的关联,实现了“有状态”的交互体验。


Session 的工作原理

Session 的工作流程可以概括为以下几个步骤:

  1. 创建/获取 Session

    Java session 管理有哪些常见方案?-图3
    (图片来源网络,侵删)
    • 当一个用户第一次访问网站,并且服务器端代码中调用了获取 Session 的方法(request.getSession())时,服务器会为这个用户创建一个新的 HttpSession 对象。
    • 如果用户不是第一次访问,并且在浏览器中携带了有效的 Session ID,服务器则会根据这个 ID 找到并返回已存在的 HttpSession 对象。
  2. 分配 Session ID

    • 服务器为新创建的 Session 生成一个唯一的、随机的 Session IDA1B2C3D4E5F6G7H8)。
  3. Session ID 的传递

    • 服务器会将这个 Session ID 通过 Cookie 的形式发送给用户的浏览器,这个 Cookie 通常有一个名称,如 JSESSIONID
    • 从这时起,只要浏览器不关闭且 Cookie 未过期,用户在同一个网站的每一次后续请求,浏览器都会自动在 HTTP 请求头中携带这个 JSESSIONID Cookie。
  4. 服务器识别与处理

    • 服务器接收到后续请求后,会检查请求头中的 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.propertiesapplication.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 的常见问题与最佳实践

常见问题

  1. Session 固定攻击

    • 问题:攻击者先获取一个有效的 Session ID,然后通过某种方式(如 XSS 漏洞)让受害者使用这个 ID 登录,一旦受害者登录,攻击者就拥有了和受害者相同的会话权限。
    • 解决:在用户成功登录后,重新生成一个新的 Session ID,并将旧 Session 中的数据复制到新 Session 中,然后销毁旧 Session,这可以切断攻击者与有效 Session ID 的关联。
  2. Session 劫持

    • 问题:攻击者窃取了合法用户的 Session ID,然后冒充该用户。
    • 解决
      • 绑定 IP 地址:将 Session ID 与客户端 IP 地址绑定,IP 发生变化,则 Session 失效,但这种方法不友好,因为用户可能会更换 IP(如从 Wi-Fi 切换到 4G)。
      • 绑定 User-Agent:将 Session ID 与浏览器的 User-Agent 字符串绑定。
      • 定期更换 Session ID。
      • 使用 HTTPS,防止 Session ID 在传输过程中被窃听。
  3. Session 溢出

    • 问题:在 Session 中存储了过多或过大的对象,导致服务器内存耗尽。
    • 解决:只将必要且轻量的数据(如用户 ID)存入 Session,而将如购物车、用户详情等大对象存储在数据库或缓存(如 Redis)中,Session 中只存储其引用 ID。
  4. 分布式环境下的 Session 共享

    • 问题:在负载均衡或微服务架构下,用户的请求可能被分发到不同的服务器,Session 存储在单台服务器的内存中,那么下一次请求可能就找不到 Session 了。
    • 解决
      • 使用共享存储:将 Session 数据集中存储在所有服务器都能访问的地方,如 RedisMemcached数据库,这是最常用和推荐的方案,Spring Session 提供了与 Redis 等存储的完美集成。
      • 粘性会话:配置负载均衡器,确保来自同一用户的请求总是被发送到同一台服务器,但这会失去负载均衡的优势,并成为单点故障的隐患。

最佳实践

  1. 只存必要信息:Session 是宝贵的服务器资源,不要将大量或非序列化的对象存入其中,最佳实践是只存一个用户标识符(如 userId),然后通过这个 ID 去数据库或缓存中查找完整信息。
  2. 设置合理的超时时间:根据业务需求设置 session.setMaxInactiveInterval(),避免 Session 长期占用服务器资源。
  3. 使用 HTTPS:始终使用 HTTPS 协议,保护 Session ID 在网络传输中的安全。
  4. 及时清理:用户退出登录时,务必调用 session.invalidate() 来销毁会话。
  5. 考虑分布式方案:在设计之初就应考虑系统是否可能扩展为分布式架构,并优先选择支持 Session 共享的方案(如 Spring Session + Redis)。
  6. 注意线程安全:Session 对象本身是线程安全的,但如果你在 Session 中存储了自定义对象,并且这些对象会被多个线程同时修改,那么你需要确保这些对象是线程安全的。
分享:
扫描分享到社交APP
上一篇
下一篇