目录
- 什么是 Session?
- 为什么登录需要 Session?
- Session 的工作原理
- Java Web 中使用 Session 的核心 API
- 实现用户登录的完整流程(代码示例)
- 安全最佳实践
- Session 与 Cookie 的区别
- 替代方案:Token (JWT)
什么是 Session?
Session(会话) 是一种服务器端的机制,用于在多个 HTTP 请求之间保存和共享特定用户的数据。

你可以把它想象成一个“私人储物柜”:
- 当用户第一次访问网站时,服务器会给他分配一个独一无二的“储物柜”(Session)。
- 服务器会给用户一张“钥匙”(Session ID),通常通过 Cookie 发送给浏览器。
- 在接下来的每一次请求中,浏览器都会自动带上这把“钥匙”。
- 服务器看到“钥匙”后,就知道该打开哪个“储物柜”,取出或存入属于该用户的数据。
这个“储物柜”里的数据就是 HttpSession 对象,我们可以用它来存储用户登录信息、购物车内容等。
为什么登录需要 Session?
HTTP 协议本身是无状态的,这意味着服务器默认不会记住任何用户的请求历史,每次请求都是独立的,服务器不知道上一个请求和当前请求是不是来自同一个用户。
登录功能需要“记忆”用户已经登录的状态,如果没有 Session,用户每点击一个页面,服务器都会认为他是个新访客,要求他重新登录,Session 解决了这个问题,让服务器能够识别出“这是已经登录的用户 A”。

Session 的工作原理
- 创建/获取 Session:当用户首次访问一个需要登录的页面时,服务器会检查请求中是否带有 Session ID,如果没有,服务器会创建一个新的
HttpSession对象,并生成一个唯一的 Session ID。 - 发送 Session ID:服务器通过一个名为
JSESSIONID的 Cookie 将这个 Session ID 发送给浏览器,浏览器会自动保存它。 - 后续请求携带 Session ID:在之后的每一次请求中,浏览器都会自动在请求头中带上
JSESSIONID这个 Cookie。 - 服务器识别用户:服务器收到请求后,会从 Cookie 中读取
JSESSIONID,然后根据这个 ID 找到对应的HttpSession对象,从而获取到之前存储的用户信息。
Java Web 中使用 Session 的核心 API
在 Java Web 开发中(无论是 Servlet 还是 Spring MVC),我们主要通过 javax.servlet.http.HttpSession 接口来操作 Session。
| 方法 | 描述 |
|---|---|
HttpSession request.getSession() |
最常用,获取当前请求的 Session,Session 不存在,则创建一个新的。 |
HttpSession request.getSession(false) |
获取当前请求的 Session,Session 不存在,则返回 null,不会创建新 Session。 |
Object HttpSession.getAttribute(String name) |
从 Session 中根据键获取值,获取登录用户对象。 |
void HttpSession.setAttribute(String name, Object value) |
向 Session 中存入键值对,将登录用户对象存入 Session。 |
void HttpSession.removeAttribute(String name) |
从 Session 中移除指定的键值对,用户退出登录时移除用户信息。 |
void HttpSession.invalidate() |
销毁整个 Session,会清空 Session 中所有数据,并使 Session ID 失效,这是用户退出登录的标准做法。 |
long HttpSession.getMaxInactiveInterval() |
获取 Session 的超时时间(秒)。 |
void HttpSession.setMaxInactiveInterval(int interval) |
设置 Session 的超时时间(秒),如果超过这个时间没有请求,Session 会自动失效。 |
实现用户登录的完整流程(代码示例)
这里我们使用最基础的 Servlet 来演示,因为它最能体现底层原理,如果你使用的是 Spring Boot,原理完全一样,只是 API 和配置方式不同。
项目结构
LoginProject
├── src
│ └── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ ├── controller
│ │ │ ├── LoginServlet.java
│ │ │ └── WelcomeServlet.java
│ │ └── model
│ │ └── User.java
│ └── webapp
│ ├── index.html
│ ├── login.html
│ └── welcome.html
步骤 1: 准备一个简单的 User 模型
User.java
package com.example.model;
public class User {
private String username;
private String password;
// 构造函数、Getter 和 Setter
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
步骤 2: 创建登录页面 (login.html)
login.html
<!DOCTYPE html>
<html>
<head>登录</title>
</head>
<body>
<h2>用户登录</h2>
<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: 编写登录处理逻辑 (LoginServlet.java)
LoginServlet.java
package com.example.controller;
import com.example.model.User;
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 {
// 模拟一个用户数据库
private User validUser = new User("admin", "123456");
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 获取用户名和密码
String username = request.getParameter("username");
String password = request.getParameter("password");
// 2. 验证用户名和密码 (实际项目中这里应该查询数据库)
if (validUser.getUsername().equals(username) && validUser.getPassword().equals(password)) {
// 登录成功
// 3. 获取 Session (如果不存在会自动创建)
HttpSession session = request.getSession();
// 4. 将用户信息存入 Session
session.setAttribute("user", validUser);
// 5. 重定向到欢迎页面
response.sendRedirect("welcome.html");
} else {
// 登录失败
response.setContentType("text/html;charset=UTF-8");
response.getWriter().println("<script>alert('用户名或密码错误!'); window.location.href='login.html';</script>");
}
}
}
步骤 4: 创建欢迎页面 (welcome.html)
这个页面需要检查用户是否已登录。
welcome.html
<!DOCTYPE html>
<html>
<head>欢迎页面</title>
</head>
<body>
<h2>欢迎您!</h2>
<p>只有登录用户才能看到此页面。</p>
<p>您可以通过浏览器开发者工具 -> Application -> Cookies 中看到 JSESSIONID。</p>
<a href="logout">退出登录</a>
</body>
</html>
步骤 5: 创建一个简单的过滤器来检查登录状态
为了保护欢迎页面,我们创建一个过滤器,在访问任何页面之前先检查 Session 中是否有用户信息。
LoginFilter.java (需要创建一个过滤器类)
package com.example.controller;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter("/*") // 过滤所有请求
public class LoginFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 获取请求的路径
String path = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length());
// 1. 如果是登录页面或登录请求,直接放行
if (path.equals("/login.html") || path.equals("/login")) {
chain.doFilter(request, response);
return;
}
// 2. 尝试获取 Session
HttpSession session = httpRequest.getSession(false); // 不自动创建
// 3. 检查 Session 中是否有用户信息
if (session != null && session.getAttribute("user") != null) {
// 用户已登录,放行
chain.doFilter(request, response);
} else {
// 用户未登录,重定向到登录页面
httpResponse.sendRedirect("login.html");
}
}
// 其他方法省略...
}
步骤 6: 创建退出登录逻辑 (LogoutServlet.java)
LogoutServlet.java
package com.example.controller;
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.html");
}
}
总结流程:
- 用户访问
welcome.html,LoginFilter检查到未登录,重定向到login.html。 - 用户输入用户名密码,提交到
LoginServlet。 LoginServlet验证成功,将User对象存入session,然后重定向到welcome.html。- 再次访问
welcome.html,LoginFilter检查到session中有用户信息,放行。 - 用户点击“退出登录”,访问
LogoutServlet,session.invalidate()被调用,Session 销毁。 - 再次访问
welcome.html,LoginFilter检查到session为空或无用户信息,再次重定向到登录页。
安全最佳实践
- 使用 HTTPS:防止 Session ID 在传输过程中被窃取(Session 劫持)。
- 设置合理的 Session 超时时间:在
web.xml中或通过代码设置,避免用户长时间不操作后 Session 仍然有效。<!-- web.xml 中配置 --> <session-config> <session-timeout>30</session-timeout> <!-- 30分钟 --> </session-config> - 重要操作后重新生成 Session ID:在用户登录成功后,可以调用
session.invalidate()销毁旧 Session,然后重新request.getSession()创建一个新 Session,这可以防止“会话固定攻击”。 - 不要在 Session 中存储敏感信息:虽然 Session 在服务器端,但如果服务器配置不当,信息可能会泄露,密码等绝对不要明文存储。
- 登出时务必销毁 Session:调用
session.invalidate()是必须的。
Session 与 Cookie 的区别
| 特性 | Cookie | Session |
|---|---|---|
| 存储位置 | 客户端(浏览器) | 服务器端 |
| 安全性 | 较低,数据可能被篡改或窃取 | 较高,数据存储在服务器,客户端只存 ID |
| 存储容量 | 通常限制在 4KB 左右 | 理论上只受服务器内存限制 |
| 生命周期 | 可长期有效(设置过期时间) | 通常较短(超时失效) |
| 依赖性 | 独立存在,不依赖 Session | 依赖 Cookie(默认通过 Cookie 传递 ID) |
关系:Session 的实现通常依赖于 Cookie,Session ID 是通过 Cookie 发送给客户端的,但 Session ID 也可以通过 URL 重写等方式传递,Cookie 是最方便的方式。
替代方案:Token (JWT)
在现代 Web 开发(尤其是前后端分离架构)中,基于 Session 的机制逐渐被 Token(特别是 JWT - JSON Web Token)所取代。
JWT 的工作方式:
- 用户登录成功后,服务器生成一个加密的 Token(字符串),里面包含了用户信息(如用户 ID、角色等)。
- 服务器将这个 Token 返回给客户端。
- 客户端在后续的每一次请求中,都在请求头(通常是
Authorization: Bearer <token>)中带上这个 Token。 - 服务器收到请求后,验证 Token 的合法性(签名是否正确、是否过期等),如果合法,就解析出用户信息,处理请求。
JWT vs Session 的优势:
- 无状态:服务器不需要存储 Session,减轻了服务器压力,这对于分布式系统尤其重要,因为 Session 需要共享(通常用 Redis 等中间件)。
- 跨域友好:Token 可以轻松地在不同域名、不同客户端(App、Web)之间共享,而 Cookie 的跨域处理比较麻烦。
- 可扩展性:更容易实现水平扩展。
对于传统的 Java Web 应用(如 JSP/Servlet),Session 仍然是一个非常成熟和简单的解决方案,但对于 RESTful API 或前后端分离项目,JWT 通常是更好的选择。
