会话技术概述
同一个客户端向服务器中发送的多个请求,需要信息共享。在做服务器开发过程中,我们的客户端和服务器之间,会有请求报文和响应报文:
HTTP 协议的无状态性:HTTP协议是无状态的,这意味着服务器默认情况下无法识别两次请求是否来自同一个客户端。
会话技术的实现方式
方式一:客户端携带信息(Cookie)
特点:客户端直接携带确切的信息,这就是客户端技术(Cookie)。
方式二:服务器端保险柜(Session)
特点:客户端只携带编号,服务器通过编号找到对应的保险柜,这就是服务器技术(Session)。
核心区别
归根结底,身份信息的维护方不同:
- 客户端技术:Cookie – 信息存储在客户端,由客户端维护
- 服务器技术:Session – 信息存储在服务器,由服务器维护
注意:Session技术是在Cookie技术基础上实现的,Session也需要通过Cookie传递会话ID。
| 特性 | Cookie | Session |
|---|---|---|
| 信息存储位置 | 客户端(浏览器) | 服务器 |
| 安全性 | 较低(可被查看/篡改) | 较高(仅在服务器存储) |
| 存储容量 | 约4KB | 无硬性限制(受服务器内存限制) |
| 生命周期 | 可设置过期时间 | 默认30分钟 |
客户端技术 Cookie


Cookie 基本概念
Cookie 是客户端(浏览器)向服务器发起请求时直接携带的信息,这些信息通过请求头中一个特殊的请求头 Cookie 携带。
Cookie 的格式
Cookie: key1=value1; key2=value2
- 携带的是键值对信息
- 值都是字符串类型
- 多组键值对使用分号
;分隔
构造 Cookie 的方式
构造 Cookie 就是让请求头 Cookie 里面包含对应的值。有三种方式:
- 浏览器构造 Cookie
- ApiFox 构造 Cookie
- 服务器构造 Cookie
浏览器构造 Cookie
步骤:
- 打开开发者工具,快捷键
F12 - 找到 应用程序(Application) 标签
- 在 存储(Storage) 中找到 Cookie

请求报文示例:
GET http://localhost:8083/demo1/cookie/fetch HTTP/1.1
Host: localhost:8083
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="110", "Not A(Brand";v="24", "Microsoft Edge";v="110"
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: zhangsan=123456; lisi=654321
第17行:Cookie: zhangsan=123456; lisi=654321
ApiFox 构造 Cookie
在 ApiFox 中可以通过 Header 直接设置 Cookie:
<div style=”text-align:center”> <img src=”../../image/04_Conversation/image-20250428201211767.png” alt=”ApiFox设置Cookie” width=”80%”/> </div>
请求报文:
GET http://localhost:8083/demo1/cookie/fetch HTTP/1.1
Cookie: zhaoliu=123456; wangwu=789987
User-Agent: PostmanRuntime/7.29.2
Accept: */*
Host: localhost:8083
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
也可以通过 Cookie 管理器 更方便地管理 Cookie:
<div style=”text-align:center”> <img src=”../../image/04_Conversation/image-20250428201453891.png” alt=”Cookie管理器” width=”80%”/> </div>
服务器构造 Cookie(重点)
流程:
- 客户端 → 服务器:发送请求
- 服务器 → 客户端:响应(携带
Set-Cookie头) - 客户端 → 服务器:后续请求自动携带 Cookie
服务器通过特殊的响应头 Set-Cookie 让浏览器设置 Cookie:
/**
* 设置 Cookie
* 访问:http://localhost:8083/demo1/cookie/set?username=lilei
*
* @author stone
* @date 2023/02/17
*/
@WebServlet("/cookie/set")
public class CookieSetServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String username = req.getParameter("username");
// 方式1:通过响应头设置(原始方式)
resp.setHeader("Set-Cookie", "username=" + username);
// 方式2:使用 Cookie 对象(推荐方式)
// Cookie cookie = new Cookie("username", username);
// resp.addCookie(cookie);
}
}
响应报文:
HTTP/1.1 200
Set-Cookie: username=lilei
Content-Length: 0
Date: Fri, 17 Feb 2023 08:15:48 GMT
Keep-Alive: timeout=20
Connection: keep-alive

推荐方式:使用 response.addCookie() 方法:
Cookie cookie = new Cookie("username", username);
resp.addCookie(cookie);
获取 Cookie
3.3.1 获取方式
@WebServlet("/cookie/fetch")
public class CookieFetchServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse resp)
throws ServletException, IOException {
// 获取所有 Cookie
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
System.out.println(name + " -> " + value);
}
}
}
}
获取单个 Cookie 的值
/**
* 获取指定名称的 Cookie 值
*/
private String getCookieValue(HttpServletRequest request, String cookieName) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookieName.equals(cookie.getName())) {
return cookie.getValue();
}
}
}
return null;
}
Cookie 的属性详解
| 属性 | 方法 | 说明 |
|---|---|---|
| name | 构造方法参数 | Cookie 的名称(必填) |
| value | 构造方法参数 | Cookie 的值(必填) |
| Path | setPath(String) | Cookie 的有效路径 |
| Domain | setDomain(String) | Cookie 的域名,用于跨域共享 |
| MaxAge | setMaxAge(int) | Cookie 的过期时间(秒) |
| HttpOnly | setHttpOnly(boolean) | 禁止 JavaScript 访问 |
| Secure | setSecure(boolean) | 仅通过 HTTPS 传输 |

Path 属性
作用:设置 Cookie 的有效路径,只有访问该路径或其子路径时,浏览器才会携带此 Cookie。
@WebServlet("/cookie/path-demo")
public class PathDemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 方式1:使用当前请求的默认路径
Cookie cookie1 = new Cookie("user1", "zhangsan");
// 默认路径为 /demo1/cookie
resp.addCookie(cookie1);
// 方式2:手动指定路径
Cookie cookie2 = new Cookie("user2", "lisi");
cookie2.setPath("/demo1"); // 整个 /demo1 应用都可访问
resp.addCookie(cookie2);
// 方式3:根路径
Cookie cookie3 = new Cookie("user3", "wangwu");
cookie3.setPath("/"); // 整个域名下都可访问
resp.addCookie(cookie3);
}
}
Domain 属性
作用:实现不同子域名间的 Cookie 共享。
@WebServlet("/cookie/domain-demo")
public class DomainDemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Cookie cookie = new Cookie("shared_data", "test_value");
// 设置父域名,子域名可共享此 Cookie
cookie.setDomain(".example.com");
cookie.setPath("/");
resp.addCookie(cookie);
}
}
域名层级示例:

注意事项:
- 不能设置与当前域名无关的 domain
- 访问 localhost 时不能设置
ccc.com这样的 domain,否则浏览器会忽略
MaxAge 属性
作用:设置 Cookie 的有效期(单位:秒)。
| 值 | 含义 |
|---|---|
| 正数 | Cookie 在指定的秒数后过期,会持久化到硬盘 |
| 负数(默认) | Cookie 为会话级别,关闭浏览器即失效 |
| 0 | 立即删除该 Cookie |
@WebServlet("/cookie/maxage-demo")
public class MaxAgeDemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 1. 会话级别 Cookie(浏览器关闭即失效)
Cookie sessionCookie = new Cookie("temp", "value");
resp.addCookie(sessionCookie);
// 2. 持久化 Cookie(7天有效)
Cookie persistentCookie = new Cookie("remember", "token123");
persistentCookie.setMaxAge(7 * 24 * 60 * 60); // 7天
resp.addCookie(persistentCookie);
// 3. 删除 Cookie
Cookie cookieToDelete = new Cookie("username", "");
cookieToDelete.setMaxAge(0);
cookieToDelete.setPath("/demo1"); // 注意:路径要与原 Cookie 一致
resp.addCookie(cookieToDelete);
}
}
安全属性(重点)
HttpOnly
作用:禁止 JavaScript 访问 Cookie,防止 XSS 攻击。
Cookie cookie = new Cookie("session_token", "secret_value");
cookie.setHttpOnly(true); // JavaScript 无法读取
resp.addCookie(cookie);
攻击场景:
// 如果没有 HttpOnly,恶意脚本可以窃取 Cookie
document.cookie; // 获取所有 Cookie
Secure
作用:Cookie 仅通过 HTTPS 协议传输,防止中间人攻击。
Cookie cookie = new Cookie("sensitive_data", "value");
cookie.setSecure(true); // 仅 HTTPS 传输
resp.addCookie(cookie);
SameSite(现代浏览器支持)
作用:控制 Cookie 在跨站请求中的发送行为,防止 CSRF 攻击。
| 值 | 说明 |
|---|---|
| Strict | 完全禁止第三方 Cookie,仅同站请求可发送 |
| Lax | 默认级别,部分跨站请求(如 GET)可发送 |
| None | 允许所有跨站请求,但必须配合 Secure |
// Tomcat 8.5+ 支持 SameSite
// 需要通过 Set-Cookie 头手动设置
resp.setHeader("Set-Cookie", "name=value; SameSite=Stric
关键点说明
- URL 编码必要性:
yyyy-MM-dd HH:mm:ss格式中的空格需要编码 - 持久化存储:设置 MaxAge 使 Cookie 在浏览器关闭后仍然有效
- 中文支持:通过 UTF-8 编码支持中文显示
常见问题与注意事项
Cookie 可以存储中文吗?
不能直接存储。Cookie 值不支持特殊字符和中文,需要使用 URL 编码:
// 存储时编码
String encoded = URLEncoder.encode("张三", "UTF-8");
Cookie cookie = new Cookie("name", encoded);
// 读取时解码
String decoded = URLDecoder.decode(cookie.getValue(), "UTF-8");
Cookie 有大小限制吗?
| 限制项 | 说明 |
|---|---|
| 单个 Cookie | 约 4KB |
| 每个域名 Cookie 数量 | 约 20-50 个(浏览器不同) |
| 总 Cookie 数量 | 约 300-400 个 |
如何删除 Cookie?
Cookie cookie = new Cookie("name", ""); // 值为空
cookie.setMaxAge(0); // 立即过期
cookie.setPath("/demo1"); // 路径必须与原 Cookie 一致
cookie.setDomain(".example.com"); // 域名必须与原 Cookie 一致
resp.addCookie(cookie);
注意:删除 Cookie 时,name、path、domain 必须与原 Cookie 完全一致。
Cookie 的安全风险
| 风险 | 说明 | 防护措施 |
|---|---|---|
| XSS 攻击 | 恶意脚本窃取 Cookie | 设置 HttpOnly |
| 中间人攻击 | 明文传输被截获 | 设置 Secure,使用 HTTPS |
| CSRF 攻击 | 跨站伪造请求 | 设置 SameSite |
| 信息泄露 | Cookie 内容被查看 | 不存储敏感信息 |
Cookie 优缺点
| 优点 | 缺点 |
|---|---|
| 减轻服务器压力 | 存储容量有限(4KB) |
| 可实现跨应用共享 | 数据类型有限(仅字符串) |
| 可设置长期有效 | 安全性较低(客户端可见) |
| 配置灵活 | 每次请求都会携带,增加流量 |
服务器技术 Session
Session 基本概念
Session 相当于每个用户在服务器上的保险柜:
- 保险柜里可以存储数据(支持敏感数据)
- 要打开保险柜需要携带钥匙(JSESSIONID)
- 钥匙丢了就打不开保险柜

Session 的工作原理
第一次请求:创建 Session
当第一次调用 request.getSession() 时:
- 服务器创建 Session 对象
- 生成唯一的 Session ID(JSESSIONID)
- 通过响应头
Set-Cookie将 JSESSIONID 发送给浏览器
HTTP/1.1 200
Set-Cookie: JSESSIONID=F1500D1D295B3953DCBBF89AD614F1E6; Path=/demo2; HttpOnly
Content-Length: 0
Date: Mon, 20 Feb 2023 06:55:17 GMT
后续请求:携带 Session ID
浏览器自动在 Cookie 中携带 JSESSIONID:
GET http://localhost:8083/demo2/hello HTTP/1.1
Host: localhost:8083
Cookie: JSESSIONID=F1500D1D295B3953DCBBF89AD614F1E6
服务器根据 JSESSIONID 找到对应的 Session 对象。

Session 的基本操作
获取 Session
// 方式1:常用方式
// 如果不存在则创建,如果存在则返回已有 Session
HttpSession session = request.getSession();
// 方式2:带参数方式
// true:同方式1
// false:不存在则返回 null,不创建新 Session
HttpSession session = request.getSession(false);
存取数据
Session 是一个键值对存储结构:
- Key:String 类型
- Value:Object 类型(可存储任意对象)
@WebServlet("/session/set")
public class SessionSetServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 获取 Session(不存在则创建)
HttpSession session = req.getSession();
// 存储数据
session.setAttribute("mobile", "18666778899");
session.setAttribute("userId", 65536);
session.setAttribute("user", new User("zhangsan", 20));
}
}
@WebServlet("/session/get")
public class SessionGetServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
HttpSession session = req.getSession();
// 获取数据
String mobile = (String) session.getAttribute("mobile");
Integer userId = (Integer) session.getAttribute("userId");
User user = (User) session.getAttribute("user");
out.println("UserId: " + userId + "<br>");
out.println("Mobile: " + mobile + "<br>");
out.println("User: " + user + "<br>");
}
}
删除数据
// 移除单个属性
session.removeAttribute("mobile");
// 清空所有数据
session.invalidate(); // 同时使 Session 失效
Session 的生命周期
Session 对象的创建与销毁
| 事件 | 触发条件 |
|---|---|
| 创建 | 第一次调用 request.getSession() |
| 销毁 | 1. 服务器关闭<br>2. 应用卸载<br>3. 调用 session.invalidate()<br>4. 超过有效期(默认30分钟) |
数据的生命周期
// 设置 Session 有效期(秒)
session.setMaxInactiveInterval(30 * 60); // 30分钟
// 获取创建时间
long createTime = session.getCreationTime();
// 获取最后访问时间
long lastAccessTime = session.getLastAccessedTime();
服务器重启与 Session 持久化
Tomcat 默认会将 Session 序列化到硬盘,服务器重启后:
- Session 对象会被反序列化重新加载
- JSESSIONID 保持不变
- 但对象引用会改变(不是同一个对象)
注意:IDEA 内置的 Tomcat 可能无法复现此功能,建议在独立 Tomcat 中测试。
常见问题分析
关闭浏览器,Session 是否被销毁?
答:Session 对象没有被销毁,仍然存在于服务器。
原因:
- 浏览器关闭后,内存中的 JSESSIONID Cookie 丢失
- 再次打开浏览器访问时,没有携带 JSESSIONID
- 服务器认为是新用户,创建新的 Session
验证方法:
// 记录下 JSESSIONID
HttpSession session = req.getSession();
System.out.println("Session ID: " + session.getId());
// 关闭浏览器后,手动在请求中携带之前的 JSESSIONID
// 仍然可以获取到原来的 Session 数据
Session 失效的常见原因
| 原因 | 说明 | 解决方案 |
|---|---|---|
| 跨域 | IP 或端口变化,JSESSIONID 无法共享 | 统一域名、使用反向代理 |
| Cookie 被禁用 | 浏览器禁用了 Cookie | 使用 URL 重写 |
| 超时 | 超过 maxInactiveInterval | 调整超时时间 |
| 手动失效 | 调用 invalidate() | 检查代码逻辑 |
| 服务器重启 | 未配置 Session 持久化 | 配置 Session 序列化 |
禁用 Cookie 后如何使用 Session?
Session 默认依赖 Cookie,如果 Cookie 被禁用,可以使用 URL 重写:
// 自动在 URL 后追加 JSESSIONID
String url = resp.encodeURL("/demo2/session/get");
// 结果:/demo2/session/get;JSESSIONID=aaaabc2849ea
resp.sendRedirect(url);
注意:现代应用中,建议要求用户启用 Cookie,URL 重写方式存在安全风险。
Session 并发问题
当同一个用户在不同浏览器/设备同时登录时:
// 方案1:允许同时登录(默认行为)
// 每个浏览器获得不同的 Session
// 方案2:限制同时登录(踢掉之前的)
// 需要配合应用层缓存实现
Session 使用建议
- 存储内容:用户 ID、登录状态、购物车等敏感/重要信息
- 有效期设置:根据业务场景设置合理的超时时间
- 安全性:敏感操作需配合其他验证机制
- 内存管理:避免在 Session 中存储大量数据
Cookie 与 Session 对比
核心区别
| 特性 | Cookie | Session |
|---|---|---|
| 存储位置 | 客户端(浏览器) | 服务器端 |
| 安全性 | 较低(可被查看/篡改) | 较高(仅在服务器) |
| 存储容量 | 约 4KB | 无硬性限制 |
| 数据类型 | 仅 String | 任意 Object |
| 生命周期 | 可设置长期有效 | 默认 30 分钟 |
| 跨应用 | 可通过 Domain/Path 共享 | 局限于当前应用 |
| 服务器压力 | 低 | 较高(占用内存) |
使用场景选择
适合使用 Cookie 的场景
- 记住用户名:7天免登录勾选框
- 用户偏好设置:主题、语言等
- 非敏感追踪数据:浏览历史(需用户同意)
- 跨应用共享数据:单点登录的 Token
适合使用 Session 的场景
- 用户登录状态:保存用户 ID、权限信息
- 敏感信息:银行卡号、身份证号等
- 购物车数据:电商网站的临时购物车
- 验证码:防止表单重复提交
会话安全
Cookie 安全
必设的安全属性
Cookie cookie = new Cookie("token", value);
// 1. HttpOnly:防止 XSS 窃取
cookie.setHttpOnly(true);
// 2. Secure:仅 HTTPS 传输
cookie.setSecure(true);
// 3. SameSite:防止 CSRF
cookie.setPath("/");
// 注意:Java Cookie API 不直接支持 SameSite,需手动设置 Header
resp.setHeader("Set-Cookie",
"token=" + value + "; HttpOnly; Secure; SameSite=Strict");
敏感信息处理
// ❌ 错误:直接在 Cookie 中存储明文密码
Cookie badCookie = new Cookie("password", "123456");
// ✅ 正确:只存储随机生成的 Token
Cookie goodCookie = new Cookie("session_token",
UUID.randomUUID().toString());
goodCookie.setHttpOnly(true);
goodCookie.setSecure(true);
Session 安全
安全建议
“java session.setMaxInactiveInterval(15 * 60); // 15分钟 “
- 设置合理的超时时间
“java // 防止 Session Fixation 攻击 request.changeSessionId(); “
- 登录后重新生成 Session ID
“java // 修改密码、转账等操作需再次验证 if (!verifyPasswordAgain(req)) { resp.sendError(403); return; } “
- 重要操作二次验证
“java // 注销时使 Session 失效 session.invalidate(); “
- 及时清理 Session
常见问题排查
Session 数据丢失
排查步骤:
- 检查浏览器是否禁用了 Cookie
- 检查域名/端口是否变化(跨域)
- 检查 Session 是否超时
- 检查是否调用了
invalidate()
Cookie 无法写入
排查步骤:
- 检查响应是否已提交(Cookie 需在响应前设置)
- 检查 Path 是否匹配
- 检查 Domain 设置是否正确
- 检查浏览器隐私设置
中文乱码
// 存储时编码
Cookie cookie = new Cookie("name",
URLEncoder.encode("张三", "UTF-8"));
// 读取时解码
String name = URLDecoder.decode(cookie.getValue(), "UTF-8");
总结
核心知识点
Cookie
- 客户端技术,存储在浏览器
- 通过请求头
Cookie携带,响应头Set-Cookie设置 - 容量限制 4KB,仅支持字符串
- 重要安全属性:HttpOnly、Secure、SameSite
Session
- 服务器技术,存储在服务端
- 通过 JSESSIONID 关联客户端
- 无容量限制,支持任意对象
- 默认有效期 30 分钟
选择原则
| 场景 | 技术选择 |
|---|---|
| 非敏感信息、长期存储 | Cookie |
| 敏感信息、临时存储 | Session |
| 用户登录状态 | Session + Cookie(JSESSIONID) |
| 记住登录 | Cookie(Token)+ Session |
常见问题
Cookie 和 Session 可以单独使用吗?
可以,但通常配合使用效果更好。
- 只用 Cookie:适合存储非敏感偏好设置
- 只用 Session:需要配合其他机制传递 Session ID
- 配合使用:最常用,Session 存敏感数据,Cookie 存标识
如何防止 Session 被劫持?
- 使用 HTTPS 防止中间人攻击
- 设置 HttpOnly 防止 XSS 窃取
- 绑定 IP/UserAgent 验证
- 设置合理的超时时间
- 重要操作二次验证
分布式环境下 Session 如何处理?
- Session 复制:Tomcat 集群同步(小集群适用)
- Session 共享:使用 Redis/Memcached 集中存储
- JWT Token:无状态认证,服务端不存储 Session
- Sticky Session:负载均衡绑定特定服务器
浏览器隐私模式对 Cookie/Session 的影响?
- 隐私模式下 Cookie 不会持久化到硬盘
- 关闭隐私窗口后所有 Cookie 被清除
- Session 在服务器端仍然存在,只是客户端丢失了 JSESSIONID
如何调试 Cookie/Session 问题?
- 使用浏览器开发者工具查看 Cookie
- 使用 ApiFox/Postman 模拟请求
- 在服务端打印 Session ID 和属性
- 使用抓包工具(Wireshark、Fiddler)分析请求头