目录
- 核心概念
- 什么是 Session?
- Session 与 Cookie 的关系
- 为什么登录需要 Session?
- 实现步骤详解
- 第一步:创建登录页面
- 第二步:创建登录处理 Servlet
- 第三步:验证用户并创建 Session
- 第四步:在需要登录的页面检查 Session
- 第五步:实现登出功能
- 完整代码示例 (Servlet & JSP)
- 现代框架中的实现 (Spring Boot)
- 最佳实践与安全注意事项
- 常见问题与解决方案
核心概念
什么是 Session?
在 Web 应用中,Session(会话) 是一种服务器端的技术,用于跟踪同一个用户在多个请求之间的状态,当用户第一次访问网站时,服务器会为其创建一个独一无二的 Session 对象,并将其 ID(一个唯一的字符串)通过 Cookie 发送给用户的浏览器,之后,浏览器每次向服务器发送请求时,都会自动携带这个 Session ID,服务器通过 ID 找到对应的 Session 对象,从而“认出”这个用户。

Session 与 Cookie 的关系
- Session (会话):数据存储在服务器端,它相对安全,因为敏感信息不会直接暴露给客户端,但服务器需要消耗内存来存储这些会话数据。
- Cookie (小甜饼):数据存储在客户端(浏览器),它通常只存储一个轻量级的文本数据,Session ID。
它们如何协作?
- 服务器创建
HttpSession对象,并生成一个唯一的 ID,12345。 - 服务器将
ID=12345这条信息以 Cookie 的形式(JSESSIONID=12345)发送给浏览器。 - 浏览器将这个 Cookie 保存在本地。
- 在接下来的请求中,浏览器会自动带上这个
JSESSIONIDCookie。 - 服务器接收到请求后,从 Cookie 中提取
JSESSIONID,然后根据这个 ID 在服务器内存中找到对应的HttpSession对象。
为什么登录需要 Session?
登录的本质是验证用户身份,一旦验证成功,我们希望用户在访问网站的“受保护”页面(如个人中心、后台管理)时,服务器能够记住他已经登录了,而不用在每一个页面都重新输入用户名和密码。
Session 就是实现这个“记忆”功能的最佳工具,我们将用户的登录信息(如用户ID、用户名)存储在 Session 对象中,当用户访问受保护页面时,我们只需检查 Session 中是否存在这些信息即可,如果存在,说明用户已登录;如果不存在,则重定向到登录页面。
实现步骤详解 (传统 Servlet/JSP 方式)
假设我们使用一个简单的 User 类来表示用户。

第一步:创建登录页面 (login.jsp)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>登录</title>
</head>
<body>
<h2>用户登录</h2>
<%-- 显示错误信息(如果存在) --%>
<c:if test="${not empty errorMessage}">
<p style="color: red;">${errorMessage}</p>
</c:if>
<form action="login" method="post">
用户名: <input type="text" name="username"><br>
密码: <input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
action="login"指定了表单提交的地址,对应一个名为login的 Servlet。method="post"表示使用 POST 方法提交数据。
第二步:创建登录处理 Servlet (LoginServlet.java)
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") // 映射 Servlet 的 URL
public class LoginServlet extends HttpServlet {
// 模拟一个用户数据库
private static final String VALID_USERNAME = "admin";
private static final String VALID_PASSWORD = "password123";
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
// 1. 验证用户名和密码
if (VALID_USERNAME.equals(username) && VALID_PASSWORD.equals(password)) {
// 2. 登录成功,获取 Session 对象
// 如果请求中已有 Session,则返回该 Session;否则创建一个新的。
HttpSession session = request.getSession();
// 3. 将用户信息存入 Session
// setAttribute(key, value)
session.setAttribute("username", username);
session.setAttribute("isLoggedIn", true);
// 4. 重定向到主页(或其他受保护页面)
// 使用重定向可以防止刷新页面时表单重复提交
response.sendRedirect("welcome.jsp");
} else {
// 5. 登录失败,将错误信息存入 request 作用域
request.setAttribute("errorMessage", "用户名或密码错误!");
// 6. 转发回登录页面,并显示错误信息
// 使用转发可以共享 request 中的数据
request.getRequestDispatcher("login.jsp").forward(request, response);
}
}
}
第三步:创建需要登录的页面 (welcome.jsp)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="javax.servlet.http.HttpSession" %>
<html>
<head>欢迎页面</title>
</head>
<body>
<%
// 1. 获取当前请求的 Session
HttpSession session = request.getSession(false); // false 表示如果不存在不创建新 Session
// 2. 检查 Session 中是否存在登录标记
if (session == null || session.getAttribute("isLoggedIn") == null) {
// Session 不存在或未登录,重定向到登录页
response.sendRedirect("login.jsp");
return; // 阻止后续代码执行
}
%>
<h2>欢迎, ${sessionScope.username}!</h2>
<p>您已成功登录。</p>
<a href="logout">退出登录</a>
</body>
</html>
request.getSession(false)是一个很好的实践,它只获取已存在的 Session,如果不存在则返回null,避免了不必要的 Session 创建。sessionScope.username是 JSTL 的写法,等同于${session.getAttribute("username")}。
第四步:实现登出功能 (LogoutServlet.java)
登出的核心是销毁 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 失效
}
// 3. 重定向到登录页面
response.sendRedirect("login.jsp");
}
}
session.invalidate()会立即清除所有与该 Session 相关的数据,并使其失效。
完整代码示例 (Servlet & JSP)
项目结构:
webapp/
├── WEB-INF/
│ ├── web.xml (可选,如果用注解则不需要)
│ └── lib/
│ └── jstl-1.2.jar (JSP 标准标签库,用于在 JSP 中使用 EL 表达式)
├── login.jsp
├── welcome.jsp
└── WEB-INF/
└── classes/
└── com/
└── example/
└── servlet/
├── LoginServlet.class
└── LogoutServlet.class
注意: 在 login.jsp 顶部需要引入 JSTL 标签库:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
现代框架中的实现 (Spring Boot)
在现代框架如 Spring Boot 中,处理 Session 的方式更加简洁和强大,框架底层依然使用 Servlet API,但提供了更高级的抽象。

配置 Session
在 application.properties 或 application.yml 中配置 Session 超时时间(单位:秒)。
# 设置 Session 超时时间为 30 分钟 server.servlet.session.timeout=1800
登录 Controller
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 或 login.jsp 模板
}
@PostMapping("/login")
public String login(@RequestParam String username,
@RequestParam String password,
HttpSession session,
RedirectAttributes redirectAttributes) {
// 验证逻辑
if ("admin".equals(username) && "password123".equals(password)) {
// 将用户信息存入 Session
session.setAttribute("username", username);
session.setAttribute("isLoggedIn", true);
return "redirect:/welcome"; // 重定向到欢迎页
} else {
// 添加重定向参数,用于显示错误信息
redirectAttributes.addFlashAttribute("errorMessage", "用户名或密码错误!");
return "redirect:/login";
}
}
@GetMapping("/welcome")
public String welcome(HttpSession session) {
// 检查 Session
if (session.getAttribute("isLoggedIn") == null) {
return "redirect:/login";
}
return "welcome"; // 返回 welcome.html 模板
}
@GetMapping("/logout")
public String logout(HttpSession session) {
session.invalidate(); // 销毁 Session
return "redirect:/login";
}
}
关键区别:
- Spring Boot 使用
@Controller注解来标记控制器。 HttpSession作为方法参数直接注入,非常方便。- 使用
RedirectAttributes来在重定向时传递数据,比 Servlet 的request.setAttribute更优雅。
最佳实践与安全注意事项
- 使用 HTTPS: 传输密码等敏感信息时,必须使用 HTTPS 协议,防止数据在传输过程中被窃听。
- 设置合理的 Session 超时时间: 默认的 Session 超时时间可能过长或过短,根据应用安全级别设置,15-30 分钟,用户长时间不活动后,应自动退出。
- 重要数据不要存入 Session: Session 存储在服务器内存中,如果用户量巨大,会消耗大量内存,在分布式/集群环境中,Session 需要特殊处理(如使用 Redis 共享 Session),只存储必要的、轻量的状态信息(如用户ID),而不要将整个用户对象或大量数据存入 Session。
- 防止 Session 固定攻击: 在用户登录成功后,最好调用
session.invalidate()销毁旧的 Session,然后创建一个新的 Session,这样可以防止攻击者利用已知 Session ID 进行攻击。// 在登录成功后 session.invalidate(); // 先销毁旧的 HttpSession newSession = request.getSession(true); // 再创建一个新的 newSession.setAttribute("username", username); - 登出时一定要销毁 Session: 确保登出功能调用
session.invalidate(),否则用户关闭浏览器后,只要 Session 未超时,别人打开浏览器仍可能处于登录状态(取决于浏览器是否保存了 JSESSIONID Cookie)。 - CSRF 防护: 对于涉及状态改变的请求(如登录、登出、转账),应该实现 CSRF (Cross-Site Request Forgery) 防护,Spring Security 等框架都内置了 CSRF 防护机制。
常见问题与解决方案
问题 1: 为什么登录成功了,但跳转后页面又说未登录?
- 原因 1: 浏览器禁用了 Cookie,Session 依赖 Cookie 来传递 ID,Cookie 被禁用,服务器无法识别用户。
- 原因 2: 代码逻辑错误,在欢迎页面检查 Session 时,写成了
request.getSession(true),这会为未登录的用户也创建一个空的 Session,导致判断失误。应该使用request.getSession(false)。 - 原因 3: 项目部署在负载均衡器或反向代理(如 Nginx)后面,配置不当导致 Session 共享失败,需要配置
sticky session或使用外部存储(如 Redis)来共享 Session。
问题 2: 如何实现“记住我”功能?
“记住我”功能不能使用普通的 Session,因为 Session 有超时时间,通常的实现方式是:
- 用户勾选“记住我”并登录成功后,除了创建 Session,还在数据库中生成一个唯一的、长时效的 Token(有效期一个月)。
- 将这个 Token 存入数据库,并与用户 ID 关联。
- 将这个 Token 写入一个长期有效的 Cookie 中,发送给浏览器。
- 之后,用户每次访问时,首先检查这个长期 Cookie。
- 如果存在,用 Token 去数据库查询。
- 如果查询成功且未过期,就自动为用户登录(创建一个短期 Session)。
- 如果查询失败或已过期,则视为未登录,重定向到登录页。
问题 3: 在分布式系统中如何处理 Session?
单个服务器的内存无法满足大规模用户的需求,且无法应对单点故障,解决方案是将 Session 数据集中存储。
- 使用 Redis: 这是最主流的方案,将 Session 数据存储在 Redis 中,所有应用服务器都连接同一个 Redis 实例,这样,无论用户的请求被哪个服务器处理,都能从 Redis 中获取到正确的 Session 数据。
- 使用数据库: 将 Session 序列化后存入关系型数据库(如 MySQL),但性能通常不如 Redis。
- 使用 Tomcat 集群: 配置 Tomcat 的集群模式,它会自动在各节点间复制 Session 数据,但这种方式对网络和性能有一定要求,不如 Redis 灵活和高效。
