杰瑞科技汇

Java session 如何正确使用与管理?

什么是 Session?为什么需要它?

想象一下你去超市购物,你推着购物车,把商品一件件放进去,这个“购物车”Session,它是一个临时的、属于你个人的存储空间。

Java session 如何正确使用与管理?-图1
(图片来源网络,侵删)

在 Web 应用中,Session 是一种服务器端的会话机制,它允许服务器在多个用户请求之间为特定用户(浏览器)保存信息。

核心问题:HTTP 是无状态的

  • HTTP 协议本身是无状态的,意味着服务器不会主动记录你之前做了什么,你第一次请求 A 页面,服务器不知道你是谁;你接着请求 B 页面,服务器也认为这是一个全新的、独立的请求。
  • 但现实应用中,我们需要记录用户状态,
    • 用户登录后,后续请求都应该知道“这个用户已经登录了”。
    • 购物车里的商品。
    • 用户在多步骤表单中填写的信息。

Session 如何解决这个问题?

  1. 首次访问:当一个用户(浏览器)首次访问服务器时,服务器会创建一个唯一的 Session 对象,并为其分配一个 Session ID(通常是一个长字符串,如 JSESSIONID=1234567890ABCDEFFEDCBA9876543210)。
  2. 响应 Cookie:服务器会将这个 Session ID 通过一个名为 JSESSIONID 的 Cookie 发送给浏览器。
  3. 后续请求:浏览器在后续的每一次请求中,都会自动带上这个 JSESSIONID Cookie。
  4. 服务器识别:服务器收到请求后,会读取 Session ID,然后在服务器内存中查找对应的 Session 对象,如果找到了,就说明这个请求属于这个用户,服务器就可以从 Session 中获取之前保存的用户信息。
  • Session 是服务器端的对象,用于存储用户会话数据。
  • Session ID 是客户端(浏览器)的凭证,用于在多次请求中标识同一个用户。
  • Cookie 是 Session ID 的载体,用于在浏览器和服务器之间传递这个凭证。

如何在 Java Web 中使用 Session?(以 Servlet 为例)

在 Java Web 开发中,最直接使用 Session 的方式是通过 Servlet API,我们主要使用 HttpSession 接口。

Java session 如何正确使用与管理?-图2
(图片来源网络,侵删)

基本操作步骤

假设我们有一个登录场景,用户登录成功后,将用户信息存入 Session。

在 Servlet 中获取 Session

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取用户提交的用户名和密码
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        // 2. 模拟验证逻辑 (实际应用中应查询数据库)
        if ("admin".equals(username) && "password".equals(password)) {
            // 3. 获取 Session 对象
            // 如果当前请求没有关联的 Session,则创建一个新的。
            HttpSession session = request.getSession();
            // 4. 向 Session 中存入数据
            session.setAttribute("user", username); // "user" 是键, username 是值
            session.setAttribute("loginTime", System.currentTimeMillis());
            // 5. 登录成功,重定向到主页
            response.sendRedirect("welcome.jsp");
        } else {
            // 登录失败,返回登录页面
            response.sendRedirect("login.html?error=1");
        }
    }
}

在其他页面或 Servlet 中获取 Session 数据

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/welcome")
public class WelcomeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取 Session 对象
        // 注意:这里使用 false,如果请求中没有 Session,则返回 null,而不是创建新的。
        // 这是为了避免未登录用户访问此页面时,服务器给他创建一个空的 Session。
        HttpSession session = request.getSession(false);
        // 2. 检查 Session 是否存在以及用户是否已登录
        if (session != null && session.getAttribute("user") != null) {
            // 3. 从 Session 中获取数据
            String username = (String) session.getAttribute("user");
            long loginTime = (Long) session.getAttribute("loginTime");
            // 4. 显示欢迎信息
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            out.println("<h1>欢迎, " + username + "!</h1>");
            out.println("<p>您于 " + new java.util.Date(loginTime) + " 登录。</p>");
            out.println("<a href='logout'>退出登录</a>");
        } else {
            // 如果未登录,重定向到登录页面
            response.sendRedirect("login.html");
        }
    }
}

销毁 Session(用户退出登录)

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取 Session
        HttpSession session = request.getSession(false);
        // 2. 销毁 Session
        if (session != null) {
            session.invalidate(); // 销毁整个 Session,并使所有关联的 Session ID 失效
            // 或者使用 session.removeAttribute("user"); // 只移除某个属性,Session 对象本身还存在
        }
        // 3. 重定向到登录页面
        response.sendRedirect("login.html");
    }
}

Session 的生命周期和配置

生命周期

  1. 创建:当调用 request.getSession()request.getSession(true) 时,如果尚不存在有效的会话,则创建一个新的会话。
  2. 活跃:当客户端持续发送带有有效 JSESSIONID 的请求时,会话保持活跃。
  3. 销毁
    • 被动销毁(超时):如果客户端在一定时间内(默认30分钟)没有发送任何请求,服务器会自动销毁该 Session,这个时间可以配置。
    • 主动销毁:调用 session.invalidate()
    • 服务器关闭或重启:所有 Session 都会被销毁。

配置 Session 超时时间

web.xml 文件中配置全局的 Session 超时时间(单位:分钟):

<session-config>
    <!-- 设置 Session 超时时间为 15 分钟 -->
    <session-timeout>15</session-timeout>
</session-config>

也可以在 Servlet 代码中动态设置:

// 设置 Session 超时时间为 10 分钟 (单位是秒)
session.setMaxInactiveInterval(10 * 60);

Session vs. Cookie

特性 Session Cookie
存储位置 服务器端 客户端(浏览器)
安全性 ,敏感数据不经过网络,只传输一个 ID。 ,数据存储在客户端,可以被用户查看、修改,甚至禁用。
存储容量 较大,只受服务器内存限制。 较小,通常限制在 4KB 左右。
生命周期 可以设置超时时间,或手动销毁。 可以设置为“会话 Cookie”(关闭浏览器即失效)或“持久化 Cookie”(可设置过期时间)。
依赖性 依赖 Cookie(默认通过 JSESSIONID Cookie 传递 ID),如果禁用 Cookie,Session 机制会失效(除非使用 URL 重写)。 独立,不依赖任何其他技术。

现代框架中的 Session(如 Spring Boot)

在现代框架(如 Spring MVC / Spring Boot)中,对 Session 的使用进行了更高层次的封装,底层原理依然是 Servlet API,但使用起来更方便。

Java session 如何正确使用与管理?-图3
(图片来源网络,侵删)

Spring Boot 中使用 Session

在 Spring Boot 中,你可以通过在 Controller 方法的参数中直接注入 HttpSession 对象来使用它。

示例:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpSession;
@Controller
public class LoginController {
    @GetMapping("/login")
    public String loginPage() {
        return "login"; // 返回 login.html 视图
    }
    @PostMapping("/login")
    public String login(@RequestParam String username,
                        @RequestParam String password,
                        HttpSession session,
                        RedirectAttributes redirectAttributes) {
        // 验证逻辑
        if ("admin".equals(username) && "password".equals(password)) {
            // 直接往 session 里存东西,和 Servlet 一样
            session.setAttribute("user", username);
            return "redirect:/welcome"; // 重定向到欢迎页
        } else {
            // 使用 RedirectAttributes 传递重定向时的数据
            redirectAttributes.addFlashAttribute("error", "用户名或密码错误");
            return "redirect:/login";
        }
    }
    @GetMapping("/welcome")
    public String welcome(HttpSession session) {
        // 从 session 里取东西
        String user = (String) session.getAttribute("user");
        if (user != null) {
            // ... 业务逻辑
            return "welcome"; // 返回 welcome.html 视图
        }
        return "redirect:/login";
    }
    @GetMapping("/logout")
    public String logout(HttpSession session) {
        // 销毁 session
        session.invalidate();
        return "redirect:/login";
    }
}

@SessionAttributes 注解 Spring MVC 还提供了 @SessionAttributes 注解,它可以将 Model 中的数据自动保存到 Session 中,并指定在哪些请求中需要从 Session 中取出数据,这在需要跨多个请求保持数据的场景下非常有用。


Session 的优缺点与最佳实践

优点

  • 安全性高:敏感数据不暴露在客户端。
  • 存储容量大:适合存储对象、列表等复杂数据。

缺点

  • 占用服务器资源:每个 Session 都会占用服务器的内存,如果用户量巨大,会给服务器带来很大压力。
  • 依赖 Cookie:如果用户禁用了浏览器 Cookie,Session 机制会失效(虽然可以通过 URL 重写来解决,但体验较差)。

最佳实践

  1. 只存必要数据:Session 中只存储少量、关键的用户信息(如用户ID、用户名),不要将大量数据或大对象(如 List, Map)存入 Session,这会消耗大量内存并影响序列化/反序列化性能。
  2. 及时清理:用户退出登录时,务必调用 session.invalidate() 或移除不再需要的属性。
  3. 注意线程安全:在 Servlet 容器中,一个 HttpSession 对象通常只被一个用户(一个线程)访问,Session 本身是线程安全的,但如果将 Session 对象传递给其他线程(如线程池),就需要自己处理同步问题。
  4. 考虑替代方案
    • 对于简单数据:可以直接使用 Cookie。
    • 对于分布式系统:如果应用部署在多台服务器上(负载均衡),传统的本地内存 Session 就不适用了,这时需要使用分布式 Session 方案,如:
      • Redis:将 Session 数据存储在 Redis 中,所有服务器都可以共享。
      • Memcached:类似 Redis。
      • Spring Session:一个优秀的 Spring 项目,可以无缝集成 Redis 等后端,实现分布式 Session 管理。
  5. 防止 Session 固定攻击:在用户登录成功后,可以重新生成一个新的 Session ID,并销毁旧的 Session ID,以防止攻击者利用已知的 Session ID 进行攻击。
// 登录成功后,防止 Session 固定攻击
HttpSession oldSession = request.getSession(false);
if (oldSession != null) {
    oldSession.invalidate();
}
HttpSession newSession = request.getSession(true);
newSession.setAttribute("user", username);
分享:
扫描分享到社交APP
上一篇
下一篇