Session-Cookie 机制:从原理到攻防实战

Session 和 Cookie,这对组合是 Web 认证的基石。我见过太多团队把登录逻辑写成一团乱麻,最后出了问题都不知道从哪查起。今天咱们就把这块彻底讲透。

Session 工作原理

说白了,HTTP 是无状态的。你发一个请求,服务器处理完就忘了你是谁。Session 就是用来记住你的。

流程其实很简单:

  1. 用户登录成功后,服务器创建一个 Session 对象
  2. 服务器给这个 Session 分配一个唯一 ID(Session ID)
  3. Session ID 通过 Set-Cookie 响应头发给浏览器
  4. 浏览器后续请求自动带上这个 Cookie
  5. 服务器根据 Cookie 里的 Session ID 找到对应的 Session 数据

核心要点:Session 数据存在服务器端,客户端只保存一个 ID。这才是安全的根本。

我曾在项目中遇到过一个问题:某团队把用户角色信息直接存在 Cookie 里,结果被人改了角色值就提权了。嗯,这就是典型的「不该放客户端的东西放客户端了」。

Session 存储方式对比

存储方式 优点 缺点 适用场景
内存存储 速度快,实现简单 重启丢失,无法水平扩展 单机开发/测试
文件存储 持久化,不占内存 IO 瓶颈,分布式困难 小规模应用
数据库存储 持久化,可扩展 查询慢,增加 DB 压力 中小规模
Redis 存储 高性能,支持过期,分布式友好 额外依赖,内存成本 生产环境首选

我个人习惯在生产环境用 Redis 存 Session。为什么?因为 Redis 的 TTL 机制天然适合 Session 过期管理,而且集群部署时 Session 共享问题直接解决了。

Cookie 属性详解

Cookie 不是随便设的。每个属性都有它的意义,设错了就是漏洞。

Secure 属性

这个属性告诉浏览器:只在 HTTPS 连接下才发送这个 Cookie。你想想看,如果用户在咖啡厅连了公共 WiFi,没有 Secure 标记的 Cookie 会被明文截获。

注意:Secure 只保证传输加密,不保证 Cookie 本身安全。如果 XSS 漏洞存在,攻击者照样能通过 JavaScript 读取 Cookie。

HttpOnly 属性

这个属性更重要。它禁止 JavaScript 访问 Cookie。也就是说,就算页面被注入了恶意脚本,document.cookie 也拿不到 Session ID。

我曾经帮一个客户做安全审计,发现他们的登录 Cookie 没设 HttpOnly。结果呢?一个存储型 XSS 漏洞直接导致所有在线用户的 Session 被窃取。那场面,真是惨不忍睹。

// 正确设置 Cookie 的示例(Node.js + Express)
res.cookie('sessionId', sessionId, {
  httpOnly: true,   // 禁止 JS 访问
  secure: true,     // 仅 HTTPS
  sameSite: 'strict', // 防止 CSRF
  maxAge: 3600000   // 1 小时过期
});

其他重要属性

属性 作用 建议值
Domain 指定哪些域名可以接收 Cookie 尽量精确,不要用通配符
Path 指定哪些路径可以发送 Cookie 设为 / 或具体路径
Max-Age Cookie 存活时间(秒) 根据业务需求设定
SameSite 控制跨站请求时是否发送 Cookie Lax 或 Strict

Session 固定攻击与防御

这个攻击手法比较隐蔽,但危害极大。

攻击原理

攻击者先自己获取一个有效的 Session ID,然后通过某种方式让用户使用这个 Session ID 去登录。用户登录成功后,这个 Session 就变成了用户的合法会话。攻击者拿着原来的 Session ID 就能直接冒充用户。

为什么会这样?因为很多系统在登录成功后,只是把 Session 的状态从「未认证」改成「已认证」,而不重新生成 Session ID。

避坑指南:我曾经在一个金融项目中看到,登录接口只校验用户名密码,校验通过后直接复用客户端传来的 Session ID。这就是典型的 Session 固定漏洞。攻击者只需要给用户发一个带 Session ID 的链接,等用户登录完,攻击者就能直接操作账户了。

攻击流程

  1. 攻击者访问网站,获取一个 Session ID(比如:abc123)
  2. 攻击者构造链接:http://example.com/login?sid=abc123
  3. 用户点击链接,浏览器带着 abc123 访问登录页
  4. 用户输入账号密码登录成功
  5. 服务器将 abc123 标记为已认证
  6. 攻击者用 abc123 直接访问,无需密码

防御方案

防御其实很简单,就一条原则:登录成功后必须重新生成 Session ID。

// Java Servlet 示例
// 登录成功后
HttpSession oldSession = request.getSession(false);
if (oldSession != null) {
    oldSession.invalidate(); // 废弃旧 Session
}
HttpSession newSession = request.getSession(true); // 创建新 Session
// 将用户信息存入新 Session
newSession.setAttribute("user", userInfo);

其他辅助措施:

  • 不接受客户端传来的 Session ID,只使用服务器生成的
  • 设置合理的 Session 过期时间
  • 绑定客户端 IP 或 User-Agent(但要注意 NAT 场景)
  • 登录页面强制使用 HTTPS

核心逻辑流程图

下面这张图把 Session-Cookie 的完整生命周期和攻击点都标出来了,建议你仔细看看。

Session-Cookie 生命周期与攻击点 Session-Cookie 生命周期与攻击点 浏览器 服务器 攻击者 ① 请求登录页 ② 返回 Set-Cookie: SID=abc123 ③ 构造恶意链接 ④ 用户点击链接 ⑤ 携带 SID=abc123 登录 ⑥ 提交用户名密码 + SID ⑦ 登录成功,SID 标记为已认证 ⑧ 攻击者用 abc123 直接访问 ⑨ 服务器返回用户数据 防御关键点 • 登录成功后必须调用 session.invalidate() 或 regenerate() • 不接受客户端传来的 Session ID • 设置 HttpOnly、Secure、SameSite 属性

生产环境最佳实践

最后,我总结一下这些年踩坑后沉淀下来的配置清单:

Session 配置清单:

  • 使用 Redis 存储 Session,设置合理的 TTL(建议 30 分钟)
  • 登录成功后强制重新生成 Session ID
  • Cookie 设置 HttpOnly + Secure + SameSite=Lax
  • Session ID 长度至少 128 位,使用安全的随机数生成器
  • 敏感操作(修改密码、转账等)要求重新认证
  • 服务端校验 Session 的 IP 和 User-Agent 一致性

嗯,这套配置我用了好几年,基本没出过 Session 相关的问题。你照着搭,至少能防住 90% 的常见攻击。