浏览器 本地存储 (cookie 、sessionStorage、localStorage)

Cookie 是服务器发送到用户浏览器并保存在本地的一小段文本数据,浏览器在后续请求中会自动携带同源的 Cookie,用于会话管理个性化设置跟踪分析

http://localhost:5177/react19-vite-app/bug-view

js 复制代码
  <button onClick={() => {
    document.cookie = "theme=dark";
    
    // 读取 cookie
    console.log(document.cookie)
  }}>
     设置 Cookie
  </button>

服务器通过响应头 Set-Cookie: 设置

js 复制代码
Set-Cookie: sessionId=abc123; Path=/; HttpOnly; Secure; SameSite=Lax

存活时间

  • Max-Age秒数(相对时间),从当前请求开始,Cookie 有效的秒数。例如 Max-Age=3600 表示 1 小时后过期。
  • Expires, HTTP-date 绝对时间(若与 Max-age 同时存在,Max-Age 优先), 指定 Cookie 过期的具体时间点(UTC)。例如 Expires=Wed, 21 Oct 2026 07:28:00 GMT
  • 未设置 ExpiresMax-Age 时,Cookie 在浏览器关闭时删除(会话结束)。

浏览器会自动管理 Cookie 的生命周期。一旦 Cookie 的过期时间到达(通过 ExpiresMax-Age 指定),浏览器会立即将其从 Cookie 存储中删除或标记为无效。

建议存储的数据类型

  • 用户登录后,服务器生成一个唯一的 sessionId 存入 Cookie,后续请求自动携带,服务器据此识别用户会话状态。
  • 用户偏好设置。例如语言选择、主题(暗色/亮色)、字体大小、页面布局模式等。

不建议存储的数据类型

  • 密码(明文或哈希都不可)
  • 支付令牌、信用卡号
  • 个人身份信息(身份证、手机号等)
  • 大体积数据(>4KB)
  • 敏感业务逻辑数据
示例 HTTP 同源 (@tinyhttp/app)

同源请求时,浏览器的 fetch 会自动携带 Cookie,不需要额外设置。

可以看到 cookie只传递了两个

示例 HTTP 跨域带上 cookies
js 复制代码
   <button onClick={() => {
    fetch('http://localhost:3000/api/cookies-1',{
      credentials: "include", // 发送 cookie
    }).then(res => {
      console.log('res',res)
    })
  }}>
    跨域请求 
  </button>

当请求带有 credentials: "include" 配置,后端 设置 access-control-allow-credentials为true。Access-Control-Allow-Origin 必须是 具体域名

js 复制代码
  app.get("/api/cookies-1", (req, res) => {
    res.setHeader("access-control-allow-credentials", "true");
    res.setHeader("access-control-allow-origin", "http://localhost:5177");
    res.send("hello cookies");
  });
示例 XMLHttpRequest 同源

同源请求:默认会自动带上 Cookie。

示例 XMLHttpRequest 跨域

跨域请求 默认不会自动带上 Cookie,需要手动设置 withCredentials = true。

js 复制代码
const ajaxFetch = (url: string, withCredentials?: boolean) => {
  const xhr = new XMLHttpRequest();
  xhr.open("GET", url);
  xhr.onreadystatechange = () => {
    if (xhr.readyState === 4) {
      console.log("xhr.responseText", xhr);
    }
  };
  if (withCredentials) {
    xhr.withCredentials = true;
  }
  xhr.send();
};
示例 HTTPS (express)

前端项目 react19 + vite 8

1、同源。fetch 默认带上 cookie

vite 配置代理

js 复制代码
  proxy: {
    "/eapi": {
      target: "https://localhost:8443", // https 项目 (express)
      changeOrigin: true,
      secure: false, // 跳过证书
      rewrite: (path) => {
        return path.replace(/^\/eapi/, "/api")
      },
    },
  },

2、跨域

当 fetch 使用 credentials: "include" 时,服务器返回头部 Access-Control-Allow-Origin 必须是 具体域名,否则会报跨域错误。

js 复制代码
app.get('/api/user', (req, res) => {
  res.header('Access-Control-Allow-Origin', 'http://localhost:5177');
  res.header('Access-Control-Allow-Credentials', 'true');
  const userId = req.query.id;
  if (userId) {
    const user = users.find(u => u.id === parseInt(userId));
    if (user) {
      res.json(user);
    } else {
      res.status(404).json({ error: 'User not found' });
    }
  } else {
    res.json(users);
  }
});
示例 HTTP/2

页面 https://localhost:5177/react19-vite-app/bug-view

1、同源

2、跨域请求发送

example1 : 跨域

js 复制代码
  <button onClick={() => {
    fetch('https://localhost:8843/api/user',{
    }).then(res => {
      console.log('res',res)
    })
  }}>
    跨域请求 Cookie
  </button>

example2: 跨域请求带上 cookie

js 复制代码
  <button onClick={() => {
    fetch('https://localhost:8843/api/user',{
      credentials: "include", // 发送 cookie
    }).then(res => {
      console.log('res',res)
    })
  }}>
    跨域请求 Cookie
  </button>

解决措施?access-control-allow-origin, 必须指定具体 域名路径,不能是 *。当跨域请求带上 凭据信息,需要设置 access-control-allow-credentials 为true。

js 复制代码
const getUser = (stream, headers) => {
  stream.respond({
    "access-control-allow-origin": "https://localhost:5177",
    "access-control-allow-credentials": true,
    "content-type": "application/json; charset=utf-8",
    ":status": 200,
  });

  const user = {
    id: Date.now(), // 使用时间戳作为唯一ID
    name: "lili",
    age: 20,
    address: "Beijing",
    email: "hyan@example.com",
    createTime: new Date().toISOString(),
  };

  stream.end(JSON.stringify(user));
};

1、 Secure 标记。确保 Cookie 仅通过 HTTPS 传输,避免在 HTTP 连接中被窃听或篡改。

2、SameSite 属性, 用于控制 Cookie 是否在跨站请求中发送 ,主要目的是防御 CSRF(跨站请求伪造)攻击

  • Strict。Cookie 仅在同站请求中发送。任何来自其他站点的请求(包括顶级导航,如点击链接跳转)都不会携带该 Cookie。
  • Lax (默认值,现代浏览器)同站请求发送;部分跨站请求允许发送,具体包括:
    • 顶级导航的 GET 请求(如点击 <a> 链接)
    • 表单 GET 提交。不允许:POST 表单提交、iframe、AJAX、img 等跨站请求。
  • None。所有跨站请求都携带 Cookie。必须同时设置 Secure 属性(即 Cookie 仅通过 HTTPS 传输),否则浏览器会拒绝。

3、 HttpOnly 标记。用于防止客户端脚本(如 JavaScript)通过 document.cookie 访问该 Cookie

只能拿到一个cookie, 因为 lastVisit 设置了 httpOnly 为true。

js 复制代码
  <button
    onClick={() => {
      console.log("cookie", document.cookie); // theme=light
    }}
  >
    cookie
  </button>

存在风险 XSS

XSS(跨站脚本攻击)是一种注入攻击,攻击者将恶意脚本(通常是 JavaScript)注入到目标网站中。当其他用户访问该网站时,恶意脚本在受害者浏览器中执行。如果 Cookie 未设置 HttpOnly 属性,则可以通过 document.cookie 被 JavaScript 读取,从而导致 Cookie 泄露。

攻击者操作流程?

  1. 注入恶意脚本:攻击者通过评论、表单输入或 URL 参数等方式,向目标网站注入一段恶意代码。
  2. 用户访问恶意页面:当受害者访问包含该脚本的页面时,浏览器执行该脚本。
  3. Cookie 被窃取 :脚本读取 document.cookie(前提是 Cookie 未设置 HttpOnly),获取到用户的身份凭证(如 sessionId),然后发送到攻击者的服务器。
  4. 攻击者利用 Cookie:攻击者拿到 Cookie 后,可将其添加到自己的请求头中,假冒受害者访问网站,执行敏感操作。

为什么 Cookie 容易成为 XSS 的目标?

  • 自动携带:Cookie 通常用于身份认证,在每次同源请求中自动发送。一旦泄露,攻击者可直接冒充用户。
  • 存储敏感信息:许多网站将 session ID 或 token 存储在 Cookie 中,这些凭证正是攻击者想要窃取的目标。
  • 默认可被脚本读取 :若未显式设置 HttpOnly,所有同源 JavaScript 都可以通过 document.cookie 读取 Cookie 内容。

Cookie 的 HttpOnly 如何防御 XSS?

设置了 HttpOnly 属性的 Cookie,浏览器会禁止 JavaScript 通过 document.cookie 访问。

存在风险 CSRF

CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种攻击方式,攻击者诱导用户访问恶意网站,该网站利用用户浏览器自动携带目标网站的 Cookie 这一特性,伪造用户发起的请求,执行非预期操作。

攻击者步骤?

  1. 用户登录信任网站 A(如银行网站),浏览器保存了 A 的认证 Cookie。
  2. 用户在不退出 A 的情况下,访问攻击者控制的恶意网站 B
  3. 网站 B 中嵌入一个隐藏请求(如 img 标签、iframe、表单自动提交等),指向 A 的敏感接口(如 https://bank.com/transfer?to=attacker&amount=10000)。
  4. 浏览器发起该请求时,会自动携带 A 的 Cookie,服务器验证通过,执行敏感操作。

攻击者不需要窃取 Cookie,只需要"借用"浏览器自动携带的能力即可。

Cookie 为何易受 CSRF 攻击?

默认同源策略 :Cookie 的自动附加机制是浏览器内置行为,无法通过前端代码禁止(除非设置 SameSite 限制跨站发送)。

防御 CSRF 的措施?

  • SameSite ,Cookie 属性, 限制跨站请求是否携带 Cookie。SameSite=Lax Strict 可阻止大部分 CSRF。
  • CSRF Token, 服务端生成随机 token 嵌入页面,每个请求需携带该 token(通常放在请求头或参数中)。
  • 验证 Referer / Origin 头 检查请求来源是否为信任域名。
  • 要求自定义请求头, 如 X-Requested-With: XMLHttpRequest,因为跨站请求无法添加自定义头(需 CORS 预检)。

sessionStorage (5-10MB)

sessionStorage 是 Web Storage API 提供的一种客户端存储机制,用于在单个浏览器标签页(或窗口)的会话期间存储键值对数据。它提供了一种比 Cookie 更简单、容量更大的临时存储方案。

数据仅在当前标签页或窗口的会话期间有效。关闭标签页或浏览器后,数据被清除。

同源 (协议、域名、端口相同)且同一标签页 。不同标签页之间不共享(即使同源)。

只能存储字符串

js 复制代码
// 存储数据
sessionStorage.setItem('key', 'value');
sessionStorage.setItem('user', JSON.stringify({ name: 'John', age: 30 }));

// 读取数据
const value = sessionStorage.getItem('key');

建议存储的数据类型

  • 用户界面状态, 折叠/展开的侧边栏状态、当前激活的 Tab 索引、列表滚动位置、模态框是否已显示过
  • 表单临时草稿, 多步骤表单中已填写的数据(用户未最终提交)
  • 临时缓存数据, 从 API 获取的、但仅在当前标签页使用的数据

不适合存储的数据

  1. 认证凭证(JWT、Access Token), 任何同源脚本(包括 XSS 攻击脚本)都能直接读取,无法像 HttpOnly Cookie 那样防止 JavaScript 访问。
  2. 用户敏感信息, 如身份证号、手机号、家庭住址、支付密码等。
  3. 长期有效的 API 密钥, 一旦被 XSS 窃取,攻击者可以长期滥用。
  4. 个人隐私数据, 任何可用于识别用户身份或进行欺诈的数据。
  5. 需要跨标签页共享的数据, sessionStorage 在不同标签页之间隔离,不适合存储需要多个标签页同时访问的信息

易受 XSS 攻击

sessionStorage 是浏览器提供的 Web 存储 API,用于在同源同一标签页 的会话期间存储键值对数据。与 Cookie 不同,sessionStorage 不支持 HttpOnly 属性 ,因此任何同源的 JavaScript 脚本都可以通过 sessionStorage.getItem() 或直接访问 sessionStorage 对象来读取其中的数据。这使得 sessionStorage 成为 XSS 攻击的窃取目标。

攻击者步骤?

  1. 注入恶意脚本:攻击者通过网站漏洞(如未过滤的评论、URL 参数、输入框)注入 JavaScript 代码。
  2. 读取 sessionStorage :恶意脚本执行 sessionStorage.getItem('token') 或遍历 sessionStorage 获取所有存储的数据。
  3. 发送到攻击者服务器:窃取的数据(例如认证令牌、用户信息)被发送到攻击者控制的服务器。
  4. 利用窃取的数据:攻击者可冒充用户发起请求,或获取敏感信息。

localStorage (5-10MB)

localStorage 是 Web Storage API 提供的客户端存储机制,用于在浏览器中持久保存 键值对数据。与 sessionStorage 相比,localStorage 的数据没有过期时间,除非主动删除,否则会一直存在。

同源 (协议、域名、端口相同)的所有标签页/窗口共享

js 复制代码
// 存储数据
localStorage.setItem('key', 'value');
localStorage.setItem('user', JSON.stringify({ name: 'John', age: 30 }));

// 读取数据
const value = localStorage.getItem('key');

// 删除单个数据
localStorage.removeItem('key');

// 清除所有数据
localStorage.clear();

当同一源的其他标签页/窗口修改 localStorage 时,当前页面会触发 storage 事件。

注意:该事件仅在不同标签页中修改时触发,在修改数据的同一标签页不触发。

建议存储数据类型

  1. 用户偏好设置:主题(暗色/亮色)、字体大小、语言选择等,永久保存。
  2. 登录状态(前端) :存储 JWT token(非敏感场合),每次请求手动添加到 Authorization 头。
  3. 缓存数据:如 API 响应结果,减少网络请求(需注意数据新鲜度)。
  4. 购物车数据(非支付场景):存储商品列表,关闭浏览器后依然存在。
  5. 用户行为记录:如教程完成度、首次访问标记等。

存在 XSS 攻击

localStoragesessionStorage 类似,都受到 同源策略 保护,且 没有类似 Cookie 的 HttpOnly 属性 。因此,只要网站存在 XSS(跨站脚本)漏洞,恶意脚本就可以直接读取 localStorage 中的所有数据,导致敏感信息泄露。

攻击者过程?

  1. 注入恶意脚本:攻击者通过评论区、搜索框、URL 参数等注入 JavaScript 代码。
  2. 读取 localStorage :恶意脚本执行 localStorage.getItem('key') 或遍历 localStorage 获取全部存储内容。
  3. 发送数据到攻击者服务器:窃取的数据被发送到攻击者控制的服务器。
  4. 利用窃取的数据:攻击者可冒充用户(如使用窃取的 JWT token)、获取用户隐私等。

Cache API

Cache API 是一种浏览器提供的持久化请求/响应缓存机制 ,主要用于缓存网络请求和对应的响应对象 。它与 localStorageIndexedDB 不同,专门设计用于存储 HTTP 请求响应(RequestResponse 对象),是构建离线 Web 应用(PWA)和优化网络性能的核心工具。

Cache API 只能在安全上下文中使用(HTTPS 或 localhost)。

响应对象不可重复使用Response 是一个流,只能读取一次。存储前需要克隆:response.clone()

MDN 文档

indexedDB

IndexedDB 是一个运行在浏览器中的非关系型(NoSQL)数据库,用于在客户端存储大量结构化数据。

MDN 文档

回顾 fetch API

js 复制代码
fetch(url,options)
js 复制代码
interface RequestInit {
  method?: string
  keepalive?: boolean
  headers?: HeadersInit
  // arrayBuffer arrayBufferView blob formData  USVString ReadableStream URLSearchParams
  body?: BodyInit | null
  // 重定向处理 'error' | 'follow' | 'manual' 
  redirect?: RequestRedirect
  integrity?: string
  // 请求信号 用于取消请求的信号
  signal?: AbortSignal | null
  // "omit" | "include" | "same-origin"
  credentials?: RequestCredentials
  // 请求模式 'cors' | 'navigate' | 'no-cors' | 'same-origin'
  mode?: RequestMode
  referrer?: string
  // 控制Referer头部的策略
  referrerPolicy?: ReferrerPolicy
  window?: null
  dispatcher?: Dispatcher
  duplex?: RequestDuplex
}

最后

相关推荐
闭关修炼啊哈3 小时前
灵感日报 · 晚报 | 2026年06月05日:品牌叙事重塑、校园防诈、AI代码规范化、考试安全检测
人工智能·安全
Irissgwe3 小时前
5-2 - HTTPS 协议原理
网络协议·http·https·非对称加密·ca·https协议原理
sugar__salt3 小时前
LLM服务HTTP接口实战:前端HTTP请求全解与项目落地
前端·网络协议·http
m0_738120723 小时前
渗透测试基础——一文详解JSONP跨域劫持漏洞原理与利用
服务器·安全·web安全·json
雪的季节3 小时前
HTTP 和 HTTPS 五大核心区别
数据库·http·https
SmileAndFun4 小时前
收到安全通报后,如何划分责任做存证溯源?
安全·网络安全·渗透测试·等保测评·溯源取证·监管通报·电子存证
智慧物业老杨4 小时前
电动自行车安全管理数智化解决方案:从风险防控到证据闭环
安全·rabbitmq
EasyGBS5 小时前
从“后厨黑箱”到“透明厨房”:国标GB28181视频平台EasyGBS平台AI视频分析如何守护舌尖上的安全
人工智能·安全·音视频