技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTBR

Cookie、localStorage 和 sessionStorage:区别、使用与安全边界

所属馆藏
Browser
文件格式
Markdown
原始路径
Browser/03-cookie、localStorage和sessionStorage 三者之间的区别以及存储、获取、
本文目录11 个章节
  1. 前言
  2. 一、Cookie 的使用方式
  3. 二、localStorage 和 sessionStorage 的使用方式
  4. 三、三者的核心区别
  5. 四、Cookie 属性与安全边界
  6. 五、应用场景如何选择?
  7. 六、跨页面通信和存储事件
  8. 七、浏览器支持与可用性检测
  9. 八、常见错误
  10. 九、总结对比表
  11. 参考资料

Cookie、localStorage 和 sessionStorage:区别、使用与安全边界

Category(分类): Browser Status: 未知

前言

前端开发中经常需要在页面刷新、页面跳转或浏览器重启后保留少量状态。常见方案包括 Cookie、localStoragesessionStorage,但它们的设计目标并不相同:

  • Cookie 是 HTTP 状态管理机制,会在符合条件的请求中自动发送给服务器;
  • localStorage 是同源页面共享的持久化字符串存储;
  • sessionStorage 是同源、同一个顶级浏览上下文中的会话级字符串存储。

原文把三者概括成“Cookie、localStorage 和 sessionStorage”,并给出了使用方式。下面保留这些基础内容,同时补充 HttpOnlySecureSameSite、存储配额、隐私分区和安全边界。

一、Cookie 的使用方式

1. 最简单的设置

document.cookie = 'token=110'

这条语句只设置一个 Cookie 的名称和值,默认路径、生命周期和跨站策略都不一定符合生产要求。更完整的客户端示例:

function setCookie(name, value, options = {}) {
  const {
    maxAge,
    path = '/',
    domain,
    sameSite = 'Lax',
    secure = location.protocol === 'https:'
  } = options

  if (sameSite.toLowerCase() === 'none' && !secure) {
    throw new Error('SameSite=None 必须配合 Secure 使用')
  }

  const parts = [
    `${encodeURIComponent(name)}=${encodeURIComponent(value)}`,
    `Path=${path}`,
    `SameSite=${sameSite}`
  ]

  if (domain) {
    parts.push(`Domain=${domain}`)
  }

  if (typeof maxAge === 'number') {
    parts.push(`Max-Age=${maxAge}`)
  }

  if (secure) {
    parts.push('Secure')
  }

  document.cookie = parts.join('; ')
}

setCookie('theme', 'dark', {
  maxAge: 60 * 60 * 24 * 30,
  sameSite: 'Lax'
})

如果使用 SameSite=None,必须同时设置 Secure,也就是只能在 HTTPS 环境中使用。document.cookie 不能设置 HttpOnly,需要由服务端通过 Set-Cookie 设置。

document.cookie 返回当前页面能够读取到的 Cookie 字符串,格式类似 name=value; other=value。原文使用 unescape() 和一个较脆弱的正则表达式;unescape() 已过时,也不能正确处理所有编码值。可以使用 encodeURIComponent() / decodeURIComponent()

function getCookie(name) {
  const encodedName = encodeURIComponent(name)

  for (const item of document.cookie.split(';')) {
    const [key, ...valueParts] = item.trim().split('=')

    if (key === encodedName) {
      const value = valueParts.join('=')

      try {
        return decodeURIComponent(value)
      } catch {
        return value
      }
    }
  }

  return null
}

console.log(getCookie('theme'))

注意:带有 HttpOnly 属性的 Cookie 仍会随匹配的 HTTP 请求发送,但不会出现在 document.cookie 中。这是登录会话 Cookie 常用的安全配置。

删除 Cookie 的本质是用相同的名称、域和路径重新设置一个已经过期的 Cookie:

function deleteCookie(name, options = {}) {
  setCookie(name, '', {
    ...options,
    maxAge: 0
  })
}

deleteCookie('theme', { path: '/' })
// 如果原 Cookie 设置了 Domain,也必须在这里传入相同的 Domain。
// deleteCookie('shared-theme', { path: '/', domain: 'example.com' })

如果原 Cookie 设置了特殊的 DomainPath,删除时必须匹配对应范围。客户端脚本不能删除 HttpOnly Cookie。

登录会话通常不应通过 document.cookie 写入,而应由服务端返回:

Set-Cookie: __Host-session=opaque-id; Path=/; Max-Age=1800; Secure; HttpOnly; SameSite=Lax

__Host- 前缀在支持的浏览器中要求 SecurePath=/,并且不能设置 Domain,可以减少被子域覆盖的风险。Cookie 仍需要服务端轮换、失效、权限校验和会话撤销,属性本身不能替代完整的认证设计。

二、localStorage 和 sessionStorage 的使用方式

两者都实现了 Storage 接口,API 基本相同。它们只能保存字符串,数字、布尔值、数组和对象需要自行序列化。

const key = 'sessionData'
const number = 120

sessionStorage.setItem(key, String(number))
sessionStorage.setItem('value2', '119')

const data = sessionStorage.getItem(key)
console.log(data, Number(data))

sessionStorage.removeItem(key)
sessionStorage.clear()

推荐使用 getItem() / setItem(),不要依赖 sessionStorage.name 这种属性访问方式:存储键可能与原生属性重名,也不利于静态检查。

1. 保存复杂数据

const userPreference = {
  theme: 'dark',
  density: 'comfortable'
}

localStorage.setItem('preference', JSON.stringify(userPreference))

const rawPreference = localStorage.getItem('preference')
const preference = rawPreference ? JSON.parse(rawPreference) : null

解析不可信或损坏的数据时需要捕获异常:

function readJson(storage, key, fallback = null) {
  try {
    const raw = storage.getItem(key)
    return raw === null ? fallback : JSON.parse(raw)
  } catch {
    return fallback
  }
}

2. 获取所有键

valueOf() 返回的是 Storage 对象本身,不是一个包含全部键值的普通对象。需要遍历时可以这样写:

function entriesOf(storage) {
  return Array.from({ length: storage.length }, (_, index) => {
    const key = storage.key(index)
    return key === null ? null : [key, storage.getItem(key)]
  }).filter(Boolean)
}

console.log(entriesOf(localStorage))

3. 处理容量异常

Web Storage 的同步写入可能抛出 QuotaExceededError,隐私模式、用户禁用存储、无效 origin 或浏览器策略也可能导致 SecurityError。生产代码不应假设 localStorage 永远可用:

function safeSetItem(type, key, value) {
  try {
    const storage = window[type]
    storage.setItem(key, value)
    return true
  } catch (error) {
    console.warn('浏览器存储不可用或容量不足', error)
    return false
  }
}

safeSetItem('localStorage', 'theme', 'dark')

三、三者的核心区别

1. 生命周期

存储方式生命周期
Cookie没有 Expires / Max-Age 时通常是会话 Cookie;设置后可持续到过期时间
localStorage通常跨浏览器重启保留,但不是绝对永久;用户清理、隐私策略或浏览器回收都可能删除
sessionStorage与当前顶级浏览上下文(通常是标签页)关联;刷新页面仍在,关闭会话后通常清除

“关闭浏览器后 Cookie 一定消失”也不绝对:浏览器的会话恢复功能可能恢复会话 Cookie。无痕模式、嵌入式 WebView 和隐私策略也可能改变生命周期。

2. 作用域

存储方式作用域
Cookie由域名、可选的 DomainPathSecureSameSite 等属性决定发送范围
localStorage同一个 origin(协议、主机名、端口)共享
sessionStorage同一个 origin + 顶级浏览上下文;不同标签页通常相互隔离

https://example.comhttp://example.com 不是同一个 origin,example.comsub.example.com 也不是同一个 origin。localStorage 不能像 Cookie 的 Domain 那样直接配置为跨子域共享;需要跨窗口通信时,应使用 postMessage、Broadcast Channel 或服务端方案。

浏览器还可能对第三方 iframe、跨站上下文和隐私模式进行存储分区或限制。因此“同源”是必要条件,但在嵌入第三方场景中还要考虑浏览器隐私策略。

3. 是否自动参与 HTTP 请求

Cookie 会在请求的域、路径、协议和跨站策略都匹配时自动加入 Cookie 请求头:

Cookie: session=opaque-id; theme=dark

它并不是“每一次请求都发送”:图片、脚本、接口等资源只有在符合 Cookie 的范围和 SameSite 规则时才会携带。Cookie 体积过大仍会增加每次匹配请求的请求头开销。

localStoragesessionStorage 不会自动发送给服务器。需要主动读取,再通过 fetch、表单或其他通信方式传输:

const theme = localStorage.getItem('theme')

fetch('/api/preferences', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ theme })
})

4. 存储容量

Cookie 通常按“每个 Cookie 约 4 KiB”以及浏览器针对每个域的数量限制来设计,具体上限由浏览器决定。它适合保存小型会话标识,不适合保存页面缓存。

localStoragesessionStorage 常见实现上限约为每个 origin、每种存储 5 MiB,但浏览器、隐私模式、嵌入上下文和未来实现可能不同。容量不是可靠的业务数据库:

  • Web Storage 只保存字符串;
  • API 是同步的,大量 JSON 读写会阻塞主线程;
  • 满额时可能抛出异常;
  • 浏览器可能清理最佳努力(best-effort)存储;
  • 大量结构化数据应考虑 IndexedDB、Cache API 或服务端。

不要把“5MB”写成业务契约,也不要通过反复写入精确探测容量,这可能造成性能问题和隐私指纹风险。

原文中的浏览器存储示意图

Cookie、localStorage、sessionStorage 数据存放示意

四、Cookie 属性与安全边界

HttpOnly

HttpOnly 阻止 JavaScript 通过 document.cookie 读取 Cookie,但 Cookie 仍会随符合条件的 fetch / XHR 请求发送。它能降低 Cookie 被 XSS 直接读取的风险,但不能阻止 XSS 代码以用户身份发起请求,所以仍要防御 XSS 和检查服务端授权。

Secure

Secure 让 Cookie 只通过 HTTPS 发送(localhost 有特殊例外)。它降低中间人窃听风险,但不能替代 HttpOnly,也不能阻止 JavaScript 读取一个没有 HttpOnly 的 Cookie。

SameSite

SameSite=StrictLaxNone 控制 Cookie 是否随跨站请求发送:

  • Strict 限制最严格,可能影响从外部链接进入网站的体验;
  • Lax 在常见导航场景更友好,很多浏览器会对未明确设置的 Cookie采用 Lax 类默认行为,但不要依赖默认值;
  • None 允许跨站发送,必须配合 Secure,应只在确实需要第三方场景时使用。

SameSite 可以降低部分 CSRF 风险,但不是所有认证和跨站场景的完整防线。修改状态的接口仍应使用 CSRF Token、Origin/Referer 校验和合理的权限检查。

DomainPath

省略 Domain 时,Cookie 通常是只发给设置它的主机的 host-only Cookie。设置父域后,可能会发送给该域的子域,因此不应随意扩大范围。

Path 控制请求路径匹配,但不是可靠的安全隔离机制。不要把敏感信息仅依赖 Path 保护。

五、应用场景如何选择?

  • 服务端登录会话标识;
  • 需要随请求自动发送的小型状态;
  • 由服务端控制生命周期和权限的会话信息;
  • 在明确理解 SameSite、CSRF、HttpOnlySecure 的前提下保存少量偏好。

Cookie 的值通常应该是不可猜测的随机会话 ID,而不是把用户信息、密码或完整权限对象直接放进去。真正的会话数据可以存放在服务端或受控的会话存储中。

localStorage 适合什么?

  • 主题、语言、列表展示密度等低敏感偏好;
  • 可重新生成的客户端缓存;
  • 用户主动选择、丢失后影响较小的草稿或状态。

localStorage 不是安全存储,也不是数据库。任何能够执行在当前 origin 下的脚本都可能读取它。不要把长期有效的访问令牌、支付信息、密码或高价值密钥直接放入其中。

sessionStorage 适合什么?

  • 当前标签页内的临时状态;
  • 多步骤表单的短期草稿;
  • 刷新后仍希望保留、但不需要跨标签页共享的数据;
  • 页面导航过程中的一次性参数。

如果需要跨标签页同步,sessionStorage 通常不是合适的工具,可以考虑 localStoragestorage 事件、Broadcast Channel 或服务端状态。

Cookie 是浏览器保存并发送的一小段数据;服务端 session 是服务器保存的会话数据。常见的服务端会话方案是:

  1. 服务端创建随机、不可预测的 session ID;
  2. 通过带 HttpOnlySecureSameSite 的 Cookie 发送 ID;
  3. 浏览器后续请求自动携带 ID;
  4. 服务端根据 ID 查找会话并重新验证权限。

因此“Cookie 存客户端、session 存服务端”是一个有帮助的入门概括,但 session ID 通常仍然需要放在 Cookie 中传输。Cookie 和 sessionStorage 中的“session”也不是同一个概念。

六、跨页面通信和存储事件

同源页面可以监听 storage 事件来感知其他文档对 localStorage 的修改:

window.addEventListener('storage', (event) => {
  if (event.key === 'theme') {
    console.log('其他页面设置了主题:', event.newValue)
  }
})

这个事件通常不会在发起修改的同一个文档中触发。它适合简单通知,不适合传输大量数据或构建复杂状态同步系统。跨标签页的实时通信可以优先考虑 Broadcast Channel:

const channel = new BroadcastChannel('app-events')

channel.postMessage({ type: 'theme-changed', value: 'dark' })

channel.addEventListener('message', (event) => {
  console.log(event.data)
})

使用后应调用 channel.close()。跨源通信则应使用 window.postMessage(),并严格校验 event.origin,不能使用 '*' 作为敏感消息的默认目标。

七、浏览器支持与可用性检测

现代浏览器对 Cookie、localStoragesessionStorage 都有较好支持,但“浏览器支持 API”不等于“当前上下文一定允许写入”。用户可能禁用 Cookie,隐私模式可能使用临时存储,file: / data: 等 origin 也可能触发异常。

document.cookie 是同步 API,频繁读取大量 Cookie 可能影响主线程;需要高频状态同步时,不要在渲染循环中反复读取它。

navigator.cookieEnabled 只能作为粗略提示,不能替代实际的读写和服务端验证。访问 window.localStorage 属性本身也可能抛出异常,因此要把取 Storage 对象的动作放到 try...catch 内:

function canUseStorage(type) {
  const key = '__storage_test__'

  try {
    const storage = window[type]
    storage.setItem(key, '1')
    storage.removeItem(key)
    return true
  } catch {
    return false
  }
}

console.log('localStorage:', canUseStorage('localStorage'))
console.log('sessionStorage:', canUseStorage('sessionStorage'))

即使检测成功,真正写入时仍要处理 QuotaExceededError,并为存储不可用准备降级路径。

八、常见错误

  1. 把 localStorage 当永久数据库:它可能被用户清理、浏览器回收或隐私策略删除;
  2. 把 token 放进 localStorage 就认为安全:XSS 可以读取它,认证设计应优先考虑受保护的 Cookie 和短期令牌;
  3. 认为 HttpOnly 能防住全部 XSS:它只保护 Cookie 读取,不能阻止恶意脚本调用当前页面的接口;
  4. 认为 Cookie 每个请求都会发送:实际还要看域、路径、协议、SameSite 和请求上下文;
  5. unescape() 解析 Cookie:该函数过时,应明确编码和解码策略;
  6. 用点号访问 Storage 键:键名冲突和可读性问题都不如 getItem()
  7. sessionStorage 误认为服务端 session:二者处于不同层;
  8. 忽略 JSON 解析和容量异常:存储数据可能损坏、超限或不可用;
  9. 把 Cookie 当作加密存储:Cookie 仍可能被窃取、伪造或通过错误的站点配置泄露,服务端必须验证所有状态。

九、总结对比表

特性CookielocalStoragesessionStorage
数据类型字符串字符串字符串
是否自动随 HTTP 请求发送符合范围和策略时是
主要作用域域、路径、协议、SameSite 等originorigin + 顶级浏览上下文
生命周期会话或显式过期通常持久,但可被清理当前标签页会话
常见容量单个约 4 KiB,具体看浏览器通常约 5 MiB,具体看浏览器通常约 5 MiB,具体看浏览器
JavaScript 是否可读HttpOnly 除外
适合保存小型会话标识低敏感偏好和可重建缓存当前标签页临时状态
主要风险CSRF、窃取、配置不当XSS 读取、同步阻塞、被清理XSS 读取、生命周期和标签页隔离误解

参考资料


457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS