Cookie 与 Session 会话全过程
HTTP 的“无状态”表示协议不会自动把两次请求识别为同一业务用户,并不表示 TCP、服务器和应用内部没有状态。Cookie 是浏览器按域名、路径和安全属性自动管理并随请求发送的小段字符串;Session 是服务端以随机会话 ID 为索引保存的状态。二者经常组合,但不是同一个东西,也不是登录的唯一方案。
学习目标
- 讲清
Set-Cookie怎样进入浏览器 Cookie Jar,后续请求为何自动携带; - 理解 Domain、Path、Expires、Max-Age、Secure、HttpOnly、SameSite 的精确边界;
- 解释
getSession()、getSession(false)、SessionId、超时、invalidate 和 ID 轮换; - 理解 Session 固定、劫持、CSRF、XSS 与重放之间的区别;
- 知道 Session attribute 也有并发和序列化问题;
- 比较粘性会话、Session 复制、Redis 集中 Session、Token/JWT;
- 能排查 Cookie 未保存、未发送、Session 找不到、频繁过期、Redis Session 失效和退出后仍可访问。
一、HTTP 无状态到底是什么意思
HTTP 请求本身包含方法、路径、头和 body。服务器收到第二个请求时,协议不会自动告诉它“这是刚才登录的用户 1001”。应用需要在请求中携带某种凭证或关联标识:
flowchart TD
A["第一次请求完成登录"] --> B["服务端建立会话状态"]
B --> C["响应Set-Cookie会话ID"]
C --> D["浏览器保存Cookie"]
D --> E["后续请求自动发送Cookie"]
E --> F["服务端按ID查Session"]
F --> G["恢复用户身份和会话状态"]无状态不等于每次都新建 TCP 连接,HTTP Keep-Alive 可以复用连接;也不能用 TCP 连接代表用户身份,因为连接会被代理复用、断开重建,同一用户可有多个连接。
二、Cookie 的两条报文
服务端设置 Cookie 使用响应头:
Set-Cookie: JSESSIONID=4F83A1...; Path=/; HttpOnly; Secure; SameSite=Lax浏览器后续发送使用请求头:
Cookie: theme=dark; JSESSIONID=4F83A1...Set-Cookie 可以出现多行,每行设置一个 Cookie;请求 Cookie 头通常把多个 name=value 组合发送。服务端不能假定 Cookie 头顺序能表达业务优先级,同名 Cookie 还可能因 Path/Domain 不同同时存在。
2.1 浏览器保存与发送决策
flowchart TD
A["收到Set-Cookie"] --> B["校验Domain和来源"]
B --> C["保存name value及属性"]
D["准备发起新请求"] --> E["筛选域名匹配Cookie"]
E --> F["筛选Path匹配Cookie"]
F --> G["检查Secure和HTTPS"]
G --> H["检查SameSite跨站规则"]
H --> I["排除过期Cookie"]
I --> J["生成Cookie请求头"]Cookie 是否发送主要由浏览器按属性决定,不是前端 JavaScript 每次手动拼接。使用 Fetch/XHR 跨源发送凭证还需客户端 credentials 与服务端 CORS 共同允许。
三、Cookie 属性的精确语义
3.1 Domain:主机范围
不设置 Domain 时通常是 Host-Only Cookie,只发送给设置它的主机,例如 app.example.com,不会发送给 api.example.com。
设置:
Domain=example.com可使其匹配 example.com 及符合规则的子域。浏览器不会允许站点给无关公共域设置 Cookie,例如 evil.com 不能为 example.com 设置。扩大 Domain 会扩大所有可接触该 Cookie 的子域攻击面,应优先使用 Host-Only。
历史资料常写前导点 .example.com;现代语义通常忽略前导点,不应把它当成安全边界差异。
3.2 Path:发送范围,不是访问控制
Path=/admin浏览器只在路径匹配时发送,但同源其他页面的脚本和网络能力不能仅靠 Path 隔离秘密。Path 是 Cookie 选择规则,不是可靠安全沙箱。
删除 Cookie 必须使用与原 Cookie 相匹配的 name、Path,必要时还包括 Domain;只设置同名空值但 Path 不同,会创建或删除另一个 Cookie,原 Cookie 仍存在。
3.3 Expires 与 Max-Age
| 属性 | 含义 |
|---|---|
| 不设置二者 | 会话 Cookie,通常浏览器会话结束时清理,但浏览器恢复会话可能保留 |
Expires=日期 | 指定绝对过期时间,受日期解析和时钟影响 |
Max-Age=秒 | 从现在起存活秒数;通常优先于 Expires |
Max-Age=0 | 立即过期,用于删除匹配 Cookie |
客户端 Cookie 过期与服务端 Session 过期是两套状态。Cookie 还在但 Session 已过期时,浏览器仍发送旧 ID,服务端找不到会话,应返回未认证,而不是悄悄恢复旧身份。
3.4 Secure
Secure 表示仅通过安全传输上下文发送,生产登录 Cookie 应配合 HTTPS。它不加密 Cookie 在浏览器存储中的值,也不阻止服务端日志错误打印值;真正保护传输依赖 TLS。
3.5 HttpOnly
HttpOnly 阻止普通 JavaScript 通过 document.cookie 读取 Cookie,降低 XSS 直接窃取会话 ID 的风险。但 XSS 仍可在用户浏览器中发起携带 Cookie 的同源请求,所以 HttpOnly 不能代替 XSS 防护、CSP 和输出编码。
3.6 SameSite
| 值 | 常见语义 | 场景 |
|---|---|---|
Strict | 跨站导航也尽量不带 | 高安全、能接受登录跳转体验影响 |
Lax | 部分顶级安全导航可带,跨站子请求通常不带 | 常见默认选择 |
None | 允许跨站发送 | 跨站嵌入/前后端跨站,必须同时 Secure |
SameSite 判断的是“站点”语义,不完全等于浏览器同源策略中的 Origin。不同子域可能跨源但同站。浏览器版本和默认策略存在演进,生产必须在目标浏览器中验证。
SameSite 是 CSRF 的防线之一,不应代替 CSRF Token、Origin/Referer 校验和敏感操作再认证。
3.7 Cookie 前缀
__Secure-:浏览器要求 Secure,并从安全来源设置;__Host-:通常要求 Secure、Path=/、不能设置 Domain,可强化 Host-Only 边界。
例如:
Set-Cookie: __Host-session=...; Path=/; Secure; HttpOnly; SameSite=Lax前缀依赖浏览器支持,服务端仍需正确配置全部属性。
3.8 容量和内容
Cookie 会随匹配请求重复上传,放入大量 JSON 会增加每次请求网络开销,还可能超过浏览器单 Cookie/单域数量限制。Cookie 只保存最小不透明标识或必要偏好,不保存密码、完整用户信息、医疗数据和明文权限。
Cookie 即使被签名也只是防篡改,不自动防读取;需要保密还要加密,并解决密钥轮换、重放和过期。通常登录 Cookie 只放随机 SessionId。
四、Session 内部模型
容器概念上维护:
SessionId → HttpSession对象HttpSession 包含:
- 创建时间;
- 最近访问时间;
- 最大不活跃间隔;
- attribute 映射;
- 是否有效等状态。
浏览器保存的通常只有 SessionId,不是整个服务端 Session。攻击者拿到有效 ID,服务端很难仅凭 ID 判断是不是原用户,因此 ID 必须高熵、不可预测、全程 HTTPS,并尽量缩短风险窗口。
五、Session 是何时创建的
5.1 getSession 两种调用
HttpSession session = request.getSession(); // 等价于 getSession(true)
HttpSession existing = request.getSession(false); // 没有时返回 nullgetSession(true) 的逻辑:
flowchart TD
A["调用getSession true"] --> B["从请求取候选SessionId"]
B --> C{"服务端存在有效Session吗"}
C -- "是" --> D["返回现有Session"]
C -- "否" --> E["生成随机新SessionId"]
E --> F["创建并保存HttpSession"]
F --> G["响应写Set-Cookie"]
G --> H["返回新Session"]getSession(false) 只查找,不创建,适合认证 Filter 和只读接口。若未登录请求全部调用 getSession(),爬虫和探活也会创建大量空 Session,增加内存或 Redis 压力。
5.2 请求中的 SessionId 来自哪里
常见来自 JSESSIONID Cookie。禁用 Cookie 时,Servlet 还支持 URL Rewriting:
/orders;jsessionid=ABC...使用 response.encodeURL/encodeRedirectURL 可在必要时添加 ID,但 URL 中的 SessionId 容易出现在浏览器历史、Referer、访问日志和分享链接中,现代安全系统通常禁用 URL 会话跟踪并要求 Cookie。
5.3 isNew 的含义
新创建且客户端尚未通过后续请求确认会话时,session.isNew() 可能为 true。它不代表“用户刚注册”或“刚登录”,只是容器会话的新旧状态,不能作为业务登录判断。
六、登录全过程与 SessionId 轮换
flowchart TD
A["用户提交账号密码"] --> B["服务端校验凭证和风控"]
B --> C{"校验成功吗"}
C -- "否" --> D["返回统一失败并限流"]
C -- "是" --> E["轮换或新建SessionId"]
E --> F["Session保存最小身份信息"]
F --> G["Set-Cookie安全属性"]
G --> H["后续请求按ID恢复身份"]Servlet 3.1 可使用:
request.changeSessionId();登录前若已有匿名 Session,认证成功后必须轮换 ID,避免会话固定攻击。老版本常先保存必要数据、invalidate 旧 Session、创建新 Session,但要谨慎迁移属性,不能把攻击者预置的敏感状态全部复制过去。
Session 中建议保存不可变的用户标识、租户 ID、认证级别或必要上下文,不直接保存巨大用户对象、数据库连接、InputStream、非序列化客户端和敏感凭证。
七、Session 生命周期、超时与失效
7.1 不活跃超时
session.setMaxInactiveInterval(30 * 60); // 秒它是最大不活跃间隔,不是从创建开始的绝对寿命。一次有效请求可能刷新访问时间,具体访问时间字段的更新时点按规范/容器理解,不能用 getLastAccessedTime() 做严格审计时钟。
长期会话还应考虑绝对过期:即使持续活动,登录超过例如 8 小时也重新认证。敏感操作可要求近期认证或 MFA,不能只看 Session 尚未超时。
7.2 invalidate
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}invalidate 使服务端会话失效,并触发解绑/销毁事件。之后再访问该 Session 的部分方法会抛 IllegalStateException。已经进入并取得用户上下文的并发请求不一定被瞬间撤回,业务关键操作仍需检查账号状态、权限版本或撤销标记。
7.3 删除客户端 Cookie
Cookie cookie = new Cookie("JSESSIONID", "");
cookie.setPath(request.getContextPath().isEmpty()
? "/" : request.getContextPath());
cookie.setMaxAge(0);
cookie.setHttpOnly(true);
cookie.setSecure(request.isSecure());
response.addCookie(cookie);删除属性必须匹配原 Path/Domain。服务端 Session 已失效时,即使 Cookie 删除失败,旧 ID 也不应恢复身份;反过来只删客户端 Cookie、不销毁服务端 Session,会留下可被窃取 ID 继续使用的窗口。
八、Session attribute 的并发问题
同一用户可同时打开多个标签页、并行 AJAX、移动端重试。多个请求线程可能并发访问同一个 HttpSession:
Integer count = (Integer) session.getAttribute("count");
session.setAttribute("count", count == null ? 1 : count + 1);这段 get-计算-set 不是原子操作,会丢更新。容器对 attribute 映射的存取不等于让 value 和复合业务线程安全。
正确方向:
- 登录身份等属性使用不可变对象,一次替换;
- 计数使用 AtomicInteger 仍要考虑分布式 Session 序列化和多实例原子性;
- 关键购物车/流程状态存数据库并用事务或版本号;
- 不对整个 Session 粗暴同步后在锁内调数据库/HTTP,容易造成同一用户请求串行和死锁;
- 需要原子更新集中 Session 时使用存储端原子能力或业务状态服务。
九、Session 安全攻击模型
9.1 Session 固定
攻击者先让受害者使用攻击者已知的 SessionId,受害者登录后服务端继续沿用该 ID,攻击者即可使用同一 ID。防护:认证成功后轮换 ID;禁止 URL 传递;不要接受客户端自定义会话 ID。
9.2 Session 劫持
攻击者通过 XSS、非 HTTPS 网络、恶意代理、日志泄漏、浏览器插件或终端控制获得有效 ID。防护:HTTPS、Secure、HttpOnly、短超时、ID 高熵、日志脱敏、设备/风险检测和敏感操作再认证。
将 Session 绑定 IP/User-Agent 只能作为风控信号:移动网络、代理和浏览器升级会变化,严格绑定会误伤;攻击者也可能处在相同出口或伪造 User-Agent。
9.3 CSRF
浏览器会自动携带目标站点 Cookie。攻击者页面可诱导浏览器向已登录站点发送修改请求,虽然攻击者未必能读取响应。防护:SameSite、CSRF Token、Origin/Referer 校验、非简单自定义头、敏感操作二次确认;GET 不应产生业务副作用。
9.4 XSS
XSS 可以在可信站点来源执行脚本。HttpOnly 能防脚本直接读取会话 Cookie,但脚本仍可代用户发请求、读取页面可见数据。必须做上下文输出编码、CSP、输入规则和依赖治理。
9.5 登录 CSRF 与会话混淆
攻击者可诱导受害者登录到攻击者账号,受害者随后上传隐私到错误账号。登录接口同样需要 CSRF/状态参数、合理 SameSite 和来源校验,不能认为“登录前没有身份所以无 CSRF”。
十、Cookie 登录与跨域
浏览器前端调用跨源 API 时需要多方同时满足:
Cookie Domain和Path匹配
+
Secure与SameSite允许
+
Fetch/XHR credentials设置允许
+
服务端Access-Control-Allow-Credentials=true
+
服务端Allow-Origin为明确来源而不是星号任何一项错误都可能表现为“登录成功但下一请求未登录”。先在浏览器 Network 中分别查看登录响应的 Set-Cookie 是否被接受、存储面板是否存在、业务请求是否真的发送 Cookie,而不是只看前端 document.cookie;HttpOnly Cookie 本来就不可见。
十一、多实例为什么丢 Session
flowchart TD
A["登录请求到实例A"] --> B["Session仅保存在A内存"]
B --> C["浏览器保存SessionId"]
C --> D["下一请求经负载均衡到实例B"]
D --> E["B查不到该SessionId"]
E --> F["表现为随机掉登录"]11.1 粘性会话
负载均衡按 Cookie/IP 等尽量把同一会话转发到原实例。
优点:改造少。
缺点:实例故障会话丢失、负载不均、扩缩容迁移复杂、不能解决本地 Session 内存压力。适合作为临时方案,不是高可用共享状态。
11.2 Session 复制
容器把 Session 变更复制到其他节点。小集群可用,但会增加网络、序列化和一致性成本;Session 大、写频繁、节点多时复制量迅速上升。属性通常需要可序列化,第三方对象和类版本变化也可能失败。
11.3 Redis 集中 Session
所有实例按 SessionId 访问共享 Redis:
flowchart TD
A["浏览器携带SessionId"] --> B["任意应用实例"]
B --> C["Session Repository"]
C --> D["Redis读取会话"]
D --> E["恢复身份并处理请求"]
E --> F["保存变更并刷新TTL"]优点:实例无本地会话绑定,扩缩容方便。
代价:每个请求可能增加 Redis I/O;Redis 故障影响认证;对象序列化和版本升级要治理;并发写可能覆盖;TTL、Cookie 过期和绝对过期要统一。
11.4 Spring Session 大致原理
Spring Session 通常通过 Filter 包装 HttpServletRequest,把 getSession() 委托给 SessionRepository;Redis 实现按 ID 读取/保存会话,并管理过期。应用代码仍使用 HttpSession API,但底层存储已替换。
链顺序很重要:认证和业务 Filter 必须在 SessionRepositoryFilter 建立包装之后读取 Session。不要同时保留容器本地 Session 与 Spring Session 两套互相冲突的身份状态。
十二、Redis Session 的一致性与可用性
- Redis 超时要短且有明确失败策略,认证请求不能无限阻塞容器线程;
- 不能在 Redis 故障时把未知会话默认当已认证;
- Session value 要控制大小,避免大 key、网络放大和序列化耗时;
- Java 原生序列化有安全、兼容和体积问题,生产应选择明确格式并做版本演进;
- 每请求刷新 TTL 会增加写流量,可采用受控刷新策略,但必须理解最大不活跃语义;
- 主从切换若丢失最近写入,用户可能暂时掉线或旧状态复现;
- logout、封禁、修改权限需要明确撤销传播;
- 多请求并发更新不同 attribute 可能出现最后写覆盖,关键业务状态不放 Session。
Redis Session 是集中状态,不等于强一致事务系统。登录态通常可接受有限故障下重新登录,但支付授权等关键决策应读取权威权限和账户状态。
十三、Session、Opaque Token、JWT 怎么选
| 方案 | 服务端状态 | 每请求验证 | 主动撤销 | 典型场景 |
|---|---|---|---|---|
| Cookie + Session | 有 | 查本地/Redis Session | 容易删除会话 | 后台管理、Web登录 |
| Opaque Token | 有 | 查授权服务器/缓存 | 容易 | API、统一认证 |
| JWT Access Token | 可无本地会话 | 验签和声明校验 | 天然较难,需短期/黑名单/版本 | 分布式 API |
Cookie 是凭证的传输/存储机制,可以装 SessionId,也可以装 token;Session 是服务端状态模型;JWT 是有签名声明的 token 格式,三者不是同一维度的互斥名词。
JWT 注意:
- 默认只编码和签名,不加密,payload 可被读取;
- 必须校验算法、签名、issuer、audience、exp、nbf 等;
- 不能信任头部算法由客户端任意决定;
- 权限变更后旧 token 在过期前可能仍有效;
- access token 应短寿命,refresh token 轮换和复用检测另行设计;
- 存在 Cookie 中仍可能有 CSRF,存 JS 可读存储中更易被 XSS 窃取。
“前后端分离必须 JWT”和“JWT 一定比 Session 安全”都是错误结论。
十四、完整认证 Session Demo
package com.example.web;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet(name = "authServlet", urlPatterns = "/auth/*")
public class AuthServlet extends HttpServlet {
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
String action = request.getPathInfo();
if ("/login".equals(action)) {
login(request, response);
} else if ("/logout".equals(action)) {
logout(request, response);
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws IOException {
HttpSession session = request.getSession(false);
Object userId = session == null ? null : session.getAttribute("userId");
if (userId == null) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"userId\":\"" + userId + "\"}");
}
private void login(HttpServletRequest request,
HttpServletResponse response) throws IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
if (!verifyCredential(username, password)) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
HttpSession session = request.getSession(true);
request.changeSessionId(); // Servlet 3.1:认证后轮换ID
session.setMaxInactiveInterval(30 * 60);
session.setAttribute("userId", "1001");
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
private void logout(HttpServletRequest request,
HttpServletResponse response) {
HttpSession session = request.getSession(false);
if (session != null) session.invalidate();
Cookie expired = new Cookie("JSESSIONID", "");
String contextPath = request.getContextPath();
expired.setPath(contextPath.length() == 0 ? "/" : contextPath);
expired.setHttpOnly(true);
expired.setSecure(request.isSecure());
expired.setMaxAge(0);
response.addCookie(expired);
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
private boolean verifyCredential(String username, String password) {
// 教学占位:真实系统使用密码哈希校验、限流、审计和统一认证服务
return "demo".equals(username) && "not-a-real-password".equals(password);
}
}Servlet Cookie API 3.1 没有统一 setSameSite,可由容器 SessionCookieConfig、框架配置或安全构造 Set-Cookie 实现;不要随意重复添加同名 Cookie。Demo 不代表可以硬编码账号密码,真实密码必须使用 Argon2/bcrypt/scrypt/PBKDF2 等慢哈希并加盐。
十五、退出登录不是只删浏览器 Cookie
完整单会话退出:
读取当前Session但不新建
↓
服务端invalidate
↓
删除匹配Path和Domain的Cookie
↓
清理安全上下文
↓
返回成功“退出所有设备”需要维护用户到会话/refresh token 的索引,批量撤销;单纯删除当前浏览器 Cookie 不能使其他设备退出。修改密码、封禁账号、权限收回要定义是否立即使所有会话失效。
并发请求可能在 logout 前已通过认证,invalidate 不会撤销已开始的数据库事务;高风险操作在真正执行前还应检查账户状态、权限版本或会话撤销时间。
十六、生产故障排查 Runbook
16.1 登录响应有 Set-Cookie,但浏览器没保存
检查浏览器 Network 的 blocked reason、Domain 是否允许、Secure 是否在 HTTPS、SameSite=None 是否同时 Secure、Cookie 是否超过限制、反向代理是否改写/删除 Set-Cookie、系统时间和浏览器策略。
16.2 浏览器存了 Cookie,但业务请求没发送
检查请求 host/path/scheme 是否匹配、Cookie 是否过期、SameSite、Fetch credentials、是否真正跨站、浏览器隐私策略。HttpOnly 只影响 JS 读取,不阻止浏览器发送。
16.3 请求发送 JSESSIONID,但服务端仍未登录
检查实例是否一致、Session 存储是否共享、Redis 中 ID/TTL/value 是否存在、Cookie 是否同时存在多个同名不同 Path、Session 是否 invalidate、反序列化失败、应用是否换了 Cookie 名/命名空间。
16.4 登录状态随机丢失
按实例 ID 和 SessionId hash 关联日志,检查是否只有某实例使用本地 Session、负载均衡粘性是否失效、Redis 延迟/切换/淘汰、Session value 过大、TTL 刷新和应用部署版本。不能只让用户“清 Cookie”。
16.5 Session 频繁过期
核对 maxInactiveInterval 单位是秒、集中存储 TTL、Cookie Max-Age、绝对过期、负载均衡和系统时间。确认哪些请求会刷新访问时间,静态资源/异步请求是否经过会话 Filter。
16.6 退出后仍能访问
确认服务端 Session 是否删除,而非只删 Cookie;删除 Cookie 的 Path/Domain 是否匹配;请求是否使用另一个同名 Cookie;是否有 JWT/Remember-Me 第二套认证;并发请求是否已在退出前通过认证;缓存是否错误缓存了私有响应。
16.7 Redis Session 内存持续增长
检查 Session 创建 TPS、未登录请求是否调用 getSession(true)、TTL 和过期键、Session value 大小、用户对象/购物车是否过大、序列化格式、爬虫和探活。按 key 大小与 TTL 分布治理,不要只扩 Redis 内存。
16.8 安全事件:疑似会话被盗
立即撤销目标会话和关联 refresh token,检查登录/访问 IP、设备、User-Agent、时间和敏感操作,轮换可能泄漏的密钥,排查 XSS、日志、代理和终端。不能通过日志记录完整 SessionId 来调查,应记录不可逆摘要或前后短片段。
十七、常见错误与后果
| 错误 | 后果 | 正确方向 |
|---|---|---|
| 认证检查调用 getSession() | 未登录流量创建大量空Session | getSession(false) |
| 登录后不轮换ID | Session固定攻击 | changeSessionId |
| 只设置 HttpOnly 就认为无XSS | XSS仍可代用户操作 | 输出编码、CSP等纵深防御 |
| SameSite 当唯一CSRF防护 | 浏览器/业务场景有边界 | Token、Origin、再认证 |
| 只删Cookie不删服务端Session | 旧ID仍有效 | invalidate + 删除Cookie |
| 删除Cookie时Path不同 | 原Cookie继续发送 | 匹配原Path/Domain |
| Session保存巨大可变对象 | 内存、序列化、并发覆盖 | 最小不可变身份信息 |
| 本地Session直接扩容实例 | 随机掉登录 | 共享Session或无状态Token |
| JWT中放敏感数据 | Payload可被读取 | 最小声明,必要时另行加密 |
| Redis故障默认放行 | 未知身份被当已认证 | 失败关闭并明确降级范围 |
十八、面试标准回答
Cookie 和 Session 的关系
Cookie 是浏览器端按域、路径和安全属性管理并自动随请求发送的字符串;Session 是服务端状态。常见模式是 Cookie 保存随机 SessionId,服务端按 ID 查 HttpSession。Cookie 也可以独立保存偏好或 token,SessionId 也有 URL 重写等传递方式,所以二者不是必然一一绑定。
getSession 与 getSession(false) 区别
前者等价于 true:没有有效会话时创建新 Session 并准备 Set-Cookie;false 只查找,不存在返回 null。认证检查应使用 false,避免爬虫和匿名请求创建大量空 Session。
Session 固定攻击怎样防
攻击者让受害者先使用攻击者已知的 SessionId,若登录后沿用,攻击者可复用身份。认证成功后必须轮换 ID,使用 HTTPS/Secure/HttpOnly,禁用 URL 会话跟踪,并限制会话生命周期。
SameSite 能完全防 CSRF 吗
不能。它减少部分跨站 Cookie 携带,但存在浏览器差异、同站不同源、顶级导航和业务集成边界。敏感写操作仍应配合 CSRF Token、Origin/Referer 校验、正确 HTTP 方法和二次认证。
多实例 Session 怎么解决
可用粘性会话、容器复制、Redis 集中 Session 或 token。粘性会话简单但实例故障丢状态;复制随节点和会话写量扩张;Redis 便于扩缩容但引入网络、序列化和可用性依赖;JWT 减少查询但主动撤销和权限实时性更复杂。
Session 是否线程安全
同一 Session 可被多个标签页和并行请求同时访问。attribute 的单次存取不意味着 value 或 get-修改-set 复合操作原子,关键业务状态应放数据库/状态服务并使用事务或版本控制。
十九、关联知识与验收
掌握验收:
- 能画出 Set-Cookie、浏览器保存、Cookie 请求头、Session 查找链;
- 能根据 Domain、Path、Secure、SameSite 判断一个 Cookie 是否发送;
- 能解释为何删除 Cookie 必须匹配 Path/Domain;
- 能演示登录前后 SessionId 轮换并说明固定攻击;
- 能解释多标签页为什么会让 Session attribute 丢更新;
- 能比较粘性、复制、Redis Session、Opaque Token 和 JWT;
- 面对“Set-Cookie 有但没登录”,能依次检查保存、发送、Session 查找和身份属性,而不是只让用户清浏览器缓存。
