核心概念:Session 是什么?
在用户登录的场景中,Session 是一种服务器端的机制,用于在多个 HTTP 请求之间保持用户的登录状态。

- 问题背景:HTTP 协议本身是无状态的,这意味着服务器无法区分两个来自同一个浏览器的请求,你第一次请求登录页面,服务器响应给你登录表单;你提交登录信息,服务器验证通过,但下一次你请求用户主页时,服务器已经不记得“刚刚验证通过的你”是谁了。
- Session 的解决方案:
- 用户成功登录后,服务器会创建一个唯一的、随机的字符串,称为 Session ID。
- 服务器将这个 Session ID 与当前登录用户的信息(如用户名、ID、角色等)一起存储在服务器内存(或数据库、Redis等)中。
- 服务器通过一个名为
JSESSIONID的 Cookie 将这个 Session ID 发送给用户的浏览器。 - 浏览器在后续的每一次请求中,都会自动携带这个
JSESSIONIDCookie。 - 服务器收到请求后,通过读取
JSESSIONID,就能在服务器端找到对应的用户信息,从而“认出”这个用户,保持其登录状态。
一个形象的比喻:
- Session ID:就像一张“会员卡”的卡号。
- 服务器端的 Session 存储:就像这家会员制商店的会员档案库。
- 浏览器:就像顾客。
- 登录过程:顾客第一次来,店员(服务器)核实身份后,发给他一张唯一的会员卡(Session ID),顾客下次再来,只要出示这张卡,店员就能在档案库里找到他的信息,为他提供服务,而无需每次都重新验证身份。
完整实现步骤(以 Servlet + JSP 为例)
这是一个经典的、易于理解的实现方式。
第 1 步:准备项目环境
确保你的项目(Maven 项目)包含了 Servlet API 和 JSP API 的依赖。
Maven pom.xml 示例:
<dependencies>
<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- JSP API -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
<!-- JSTL (可选,但推荐用于在JSP中写Java代码) -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
第 2 步:创建登录页面 (login.jsp)
这是一个简单的表单,用于收集用户名和密码。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">登录页面</title>
</head>
<body>
<h2>用户登录</h2>
<%-- 显示错误信息 --%>
<% if (request.getAttribute("errorMsg") != null) { %>
<p style="color:red;">${errorMsg}</p>
<% } %>
<form action="login" method="post">
用户名: <input type="text" name="username"><br>
密码: <input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
第 3 步:创建登录处理 Servlet (LoginServlet.java)
这是登录逻辑的核心,它接收表单数据,进行验证,然后决定是创建 Session 还是重定向回登录页。
import java.io.IOException;
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;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
// 模拟一个用户数据库
private static final String VALID_USERNAME = "admin";
private static final String VALID_PASSWORD = "password123";
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 获取表单提交的用户名和密码
String username = request.getParameter("username");
String password = request.getParameter("password");
// 2. 验证用户名和密码
if (VALID_USERNAME.equals(username) && VALID_PASSWORD.equals(password)) {
// 3. 验证成功,获取或创建一个 Session
HttpSession session = request.getSession();
// 4. 将用户信息存入 Session
// 这是关键步骤!将用户标识存储在 session 中
session.setAttribute("user", username);
session.setAttribute("userId", 1L); // 也可以存用户ID等其他信息
// 5. 重定向到主页
response.sendRedirect("welcome.jsp");
} else {
// 6. 验证失败,设置错误信息并转发回登录页
request.setAttribute("errorMsg", "用户名或密码错误!");
request.getRequestDispatcher("login.jsp").forward(request, response);
}
}
}
代码解释:
request.getSession():这是获取 Session 的核心方法,如果请求中已有有效的JSESSIONID,则返回对应的 Session 对象,如果没有,则创建一个新的 Session 对象。session.setAttribute("user", username):将登录成功的用户名存入 Session,之后在应用的任何地方,只要能拿到HttpSession对象,就能通过getAttribute("user")获取到这个用户名。response.sendRedirect("welcome.jsp"):使用重定向,这会让浏览器重新请求welcome.jsp,并且浏览器地址栏的 URL 也会改变,这是登录成功后的标准做法。request.getRequestDispatcher(...).forward(...):使用请求转发,这是登录失败后的做法,它不会改变浏览器地址栏的 URL,只是将请求在服务器内部传递给另一个资源(如login.jsp)。
第 4 步:创建需要登录才能访问的页面 (welcome.jsp)
这个页面会检查 Session 中是否存在用户信息,如果不存在,则说明用户未登录,应被重定向到登录页。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">欢迎页面</title>
</head>
<body>
<h2>欢迎您!</h2>
<%-- 检查 session 中是否有 user 属性 --%>
<c:if test="${empty sessionScope.user}">
<%-- 如果没有,说明用户未登录,重定向到登录页 --%>
<c:redirect url="login.jsp" />
</c:if>
<%-- 如果有,显示欢迎信息 --%>
<p>欢迎, ${sessionScope.user}!</p>
<p>您的用户ID是: ${sessionScope.userId}</p>
<a href="logout">退出登录</a>
</body>
</html>
第 5 步:创建登出处理 Servlet (LogoutServlet.java)
登出的核心就是销毁 Session。
import java.io.IOException;
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;
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 获取当前 Session
HttpSession session = request.getSession(false); // false 表示如果不存在 session,则不创建
// 2. session 存在,则销毁它
if (session != null) {
session.invalidate(); // 销毁整个 session,所有绑定在 session 上的数据都会被清除
}
// 3. 重定向到登录页
response.sendRedirect("login.jsp");
}
}
代码解释:
request.getSession(false):以“不创建”模式获取 Session,这通常用于登出或检查登录状态,因为我们不想在用户未登录时创建一个无用的 Session。session.invalidate():这是销毁 Session 的方法,调用此方法后,服务器会立即删除该 Session 对象及其所有数据,并使对应的JSESSIONID失效。
最佳实践和注意事项
- 不要在 Session 中存放大对象:Session 存储在服务器内存中,如果存放大对象(如
List<BigObject>),会占用大量内存,影响服务器性能,甚至导致内存溢出,Session 应只存必要的、轻量级的用户标识信息(如userId,username,roles)。 - 设置 Session 超时:用户可能忘记登出,导致 Session 长期有效,有安全隐患,应该在
web.xml中配置 Session 的超时时间。<session-config> <session-timeout>30</session-timeout> <!-- 单位是分钟,30分钟 --> </session-config> - 使用 HTTPS:Session ID 通过 Cookie 传输,如果使用 HTTP,这些数据是明文的,容易被窃取(Session 劫持),务必在生产环境中使用 HTTPS 来保护 Session ID 的安全。
- 防止 Session 固定攻击:在用户登录成功后,可以调用
session.invalidate()创建一个全新的 Session,然后将用户信息存入这个新 Session,这样可以避免攻击者利用之前已知的 Session ID 来冒充用户。// 在 LoginServlet 中,验证成功后 HttpSession oldSession = request.getSession(false); if (oldSession != null) { oldSession.invalidate(); // 销毁旧的(可能未登录的)session } HttpSession newSession = request.getSession(true); // 创建一个新的 session newSession.setAttribute("user", username);
现代框架中的 Session (如 Spring Boot)
在现代框架如 Spring Boot 中,底层原理完全相同,但使用方式更加简化和高级。
- 自动配置:Spring Boot 自动配置了
HttpSession,你无需手动配置任何东西。 - 注入
HttpSession:你可以在 Controller 的方法参数中直接注入HttpSession对象,框架会自动为你提供。 - 注解驱动:Spring 提供了更强大的注解来处理会话和权限,如
@SessionAttributes和@PreAuthorize。
Spring Boot Controller 示例:
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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) && "password123".equals(password)) {
session.setAttribute("user", username);
return "redirect:/welcome"; // 登录成功,重定向到欢迎页
} else {
// 使用 RedirectAttributes 将错误信息传递到重定向的页面
redirectAttributes.addFlashAttribute("errorMsg", "用户名或密码错误!");
return "redirect:/login"; // 登录失败,重定向回登录页
}
}
// 需要登录才能访问的页面
@GetMapping("/welcome")
public String welcome(HttpSession session, Model model) {
// 从 session 中获取用户信息
String user = (String) session.getAttribute("user");
if (user == null) {
return "redirect:/login"; // 如果未登录,重定向到登录页
}
model.addAttribute("username", user);
return "welcome"; // 返回 welcome.html 视图
}
// 处理登出
@GetMapping("/logout")
public String logout(HttpSession session) {
session.invalidate(); // 销毁 session
return "redirect:/login"; // 重定向到登录页
}
}
Session 的替代方案:JWT (JSON Web Token)
随着前后端分离架构的流行,Session + Cookie 的模式不再是唯一选择,JWT 是目前非常流行的一种无状态认证方案。
- 核心思想:不再将用户信息存储在服务器端的 Session 中,而是将用户信息(经过加密和签名)生成一个 Token,然后把这个 Token 返回给客户端,客户端在后续的每一次请求中,都通过 HTTP Header(如
Authorization: Bearer <token>)将 Token 发送给服务器,服务器只需验证 Token 的合法性即可,无需查询 Session 存储。 - 优点:
- 无状态:服务器不需要存储 Session,易于扩展,适合分布式和微服务架构。
- 跨域友好:Token 可以轻松地在不同域名之间传递。
- 移动端友好:不依赖 Cookie,更适合移动 App。
简单对比:
| 特性 | Session (服务器端) | JWT (客户端) |
|---|---|---|
| 状态 | 有状态 | 无状态 |
| 存储位置 | 服务器内存/数据库/Redis | 客户端 (LocalStorage, 内存) |
| 扩展性 | 较差,需要共享 Session 存储 | 极好,无状态,易于水平扩展 |
| 安全性 | 依赖 Cookie,易受 CSRF 攻击 | 无 Cookie,无 CSRF 风险,但需防止 Token 窃取 |
| 实现复杂度 | 相对简单 | 相对复杂,需要处理加密和签名 |
对于传统的 Java Web 应用(如基于 JSP/Servlet 的项目),Session + Cookie 是实现登录认证最直接、最经典的方式。
- 登录:验证成功后,
request.getSession()获取 Session,并用session.setAttribute()存储用户信息。 - 权限校验:在每个受保护的页面,检查
session.getAttribute("user")是否存在。 - 登出:调用
session.invalidate()销毁 Session。
对于现代化的、前后端分离的应用,JWT 正在成为更主流的选择,因为它更符合 RESTful 架构的无状态特性。
理解 Session 的核心原理是掌握 Web 应用认证的基础,无论你使用的是传统的 Servlet 还是现代的 Spring Boot。
