1.核心区别对比表
| 特性 | Cookie | Session |
|---|---|---|
| 存储位置 | 客户端浏览器 | 服务器端 |
| 存储内容 | 文本数据(key-value) | 对象数据(通常有Session ID) |
| 安全性 | 较低(可被篡改) | 较高(服务器控制) |
| 存储大小 | 每个域名通常 ≤ 4KB | 仅受服务器限制 |
| 生命周期 | 可设置过期时间 | 通常浏览器关闭或超时 |
| 性能影响 | 每次请求自动携带 | 服务器需要查询存储 |
| 跨域支持 | 有限制(同源策略) | 不受限制(服务器间) |
2.Cookie 详解
2.1Cookie 的结构
Set-Cookie: name=value; Expires=Wed, 21 Oct 2025 07:28:00 GMT; Max-Age=3600; Domain=example.com; Path=/; Secure; HttpOnly; SameSite=Strict2.2Cookie 的属性和作用
const cookieAttributes = { // 基础属性 name: "sessionId", value: "abc123xyz", // 有效期控制 expires: "2025-12-31T23:59:59Z", // 绝对时间 maxAge: 3600, // 相对时间(秒) // 作用域控制 domain: ".example.com", // 可作用于子域名 path: "/api", // 指定路径 // 安全属性 secure: true, // 仅HTTPS传输 httpOnly: true, // 禁止JavaScript访问 sameSite: "Strict", // 防止CSRF // 新属性 partitioned: true, // 第一方隔离 priority: "High" // 存储优先级 };2.3Cookie 如何传递给后端
方式1:浏览器自动携带
# 请求头中的 Cookie GET /api/user HTTP/1.1 Host: example.com Cookie: sessionId=abc123xyz; username=johndoe; theme=dark Connection: keep-alive方式2:手动设置(JavaScript)
// 1. 设置 Cookie document.cookie = "username=john; path=/; max-age=3600"; // 2. 读取 Cookie const cookies = document.cookie.split(';').reduce((acc, cookie) => { const [key, value] = cookie.trim().split('='); acc[key] = value; return acc; }, {}); // 3. 删除 Cookie(设置过期时间) document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; // 4. 在请求中手动添加 Cookie fetch('/api/data', { headers: { 'Cookie': 'sessionId=abc123' } });方式3:服务器端设置
// Node.js Express app.get('/login', (req, res) => { // 设置 Cookie res.cookie('sessionId', 'abc123', { maxAge: 24 * 60 * 60 * 1000, // 24小时 httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict' }); // 设置多个 Cookie res.cookie('user', JSON.stringify({ id: 1, name: 'John' }), { maxAge: 900000 }); });3.Session 详解
3.1Session 工作原理
客户端 服务器 | | |--- 1. 登录请求 ---------->| | | | 2. 创建Session ---------| | sessionId = "abc123" | | sessionStore[sessionId] = {userId: 1} | | | |<-- 3. 返回Set-Cookie -----| | Set-Cookie: sessionId=abc123 | | |--- 4. 携带Cookie请求 ----->| | Cookie: sessionId=abc123 | | | | 5. 验证Session ---------| | user = sessionStore[sessionId] | | |<-- 6. 返回数据 ------------|3.2Session 存储方式
// 1. 内存存储(不推荐生产环境) const sessionStore = new Map(); // 2. Redis 存储(推荐) const redisSessionStore = { async set(sessionId, sessionData) { await redisClient.set( `session:${sessionId}`, JSON.stringify(sessionData), 'EX', 3600 // 1小时过期 ); }, async get(sessionId) { const data = await redisClient.get(`session:${sessionId}`); return data ? JSON.parse(data) : null; } }; // 3. 数据库存储 const dbSessionStore = { async set(sessionId, sessionData) { await db.sessions.upsert({ where: { sessionId }, update: { data: JSON.stringify(sessionData), expiresAt: new Date(Date.now() + 3600000) }, create: { sessionId, data: JSON.stringify(sessionData), expiresAt: new Date(Date.now() + 3600000) } }); } };4.完整登录流程示例
4.1基于 Session 的认证流程
// 前端代码(React) const LoginForm = () => { const [credentials, setCredentials] = useState({ username: '', password: '' }); const handleLogin = async () => { try { // 1. 发送登录请求 const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(credentials), credentials: 'include' // 关键!允许携带Cookie }); // 2. 检查响应状态 if (response.ok) { // 3. 后续请求会自动携带Cookie const userData = await fetch('/api/user/profile', { credentials: 'include' }).then(res => res.json()); console.log('登录成功', userData); } } catch (error) { console.error('登录失败', error); } }; return ( <form onSubmit={(e) => { e.preventDefault(); handleLogin(); }}> <input value={credentials.username} onChange={(e) => setCredentials({...credentials, username: e.target.value})} /> <input type="password" value={credentials.password} onChange={(e) => setCredentials({...credentials, password: e.target.value})} /> <button type="submit">登录</button> </form> ); };4.2后端代码(Node.js + Express)
const express = require('express'); const session = require('express-session'); const RedisStore = require('connect-redis')(session); const redis = require('redis'); const app = express(); // 1. 创建Redis客户端 const redisClient = redis.createClient({ host: 'localhost', port: 6379 }); // 2. 配置Session中间件 app.use(session({ store: new RedisStore({ client: redisClient }), secret: 'your-secret-key', resave: false, saveUninitialized: false, cookie: { maxAge: 24 * 60 * 60 * 1000, // 24小时 httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict' } })); // 3. 登录路由 app.post('/api/login', (req, res) => { const { username, password } = req.body; // 验证用户 const user = authenticateUser(username, password); if (user) { // 将用户信息存入session req.session.userId = user.id; req.session.username = user.username; req.session.role = user.role; // 设置登录时间 req.session.loginTime = new Date(); res.json({ success: true, message: '登录成功', user: { id: user.id, username: user.username } }); } else { res.status(401).json({ success: false, message: '登录失败' }); } }); // 4. 受保护的路由 app.get('/api/user/profile', (req, res) => { // 检查session if (!req.session.userId) { return res.status(401).json({ error: '未登录' }); } // 获取用户数据 const userData = getUserById(req.session.userId); res.json(userData); }); // 5. 登出 app.post('/api/logout', (req, res) => { req.session.destroy((err) => { if (err) { return res.status(500).json({ error: '登出失败' }); } // 清除客户端Cookie res.clearCookie('connect.sid'); res.json({ success: true, message: '已登出' }); }); });5.Cookie 传输的常见问题与解决方案
5.1跨域请求携带 Cookie
// 前端设置 fetch('https://api.example.com/data', { method: 'GET', credentials: 'include', // 关键!允许发送Cookie headers: { 'Content-Type': 'application/json', } }); // 后端设置(CORS配置) app.use(cors({ origin: 'https://frontend.com', // 允许的源 credentials: true, // 允许携带凭证 methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'] })); // 或者手动设置响应头 app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', 'https://frontend.com'); res.header('Access-Control-Allow-Credentials', 'true'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); next(); });5.2SameSite 属性详解
// 不同SameSite策略的影响 const sameSiteStrategies = { Strict: { description: "完全禁止跨站Cookie", useCase: "银行、支付等敏感操作", example: "SameSite=Strict" }, Lax: { description: "允许部分安全的跨站请求(如导航)", useCase: "默认值,适合大多数场景", example: "SameSite=Lax", allowedRequests: [ "从外部链接点击进入(GET)", "页面刷新", "预加载请求" ] }, None: { description: "允许所有跨站请求", useCase: "需要跨站功能的场景(如SSO)", requirements: [ "必须同时设置Secure属性", "必须使用HTTPS" ], example: "SameSite=None; Secure" } };5.3Cookie 大小限制与优化
// 优化策略 const cookieOptimization = { // 1. 减少Cookie数量 strategies: [ "合并多个Cookie为一个", "使用Session替代大量Cookie", "将非必要数据存储到LocalStorage" ], // 2. 压缩Cookie值 compressCookie: (data) => { const jsonStr = JSON.stringify(data); // 使用base64编码压缩 return Buffer.from(jsonStr).toString('base64'); }, // 3. 按需发送Cookie setupCookies: () => { // 主域名设置通用Cookie document.cookie = "theme=dark; domain=.example.com; path=/"; // 子域名设置特定Cookie document.cookie = "api_token=xyz; domain=api.example.com; path=/"; // 特定路径设置Cookie document.cookie = "cart_items=3; path=/shop"; }, // 4. 定期清理过期Cookie cleanupCookies: () => { const cookies = document.cookie.split(';'); cookies.forEach(cookie => { const [name, value] = cookie.trim().split('='); // 检查并删除过期Cookie if (isCookieExpired(name)) { document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`; } }); } };6.现代认证方案对比
6.1传统 Session vs JWT
// JWT 认证流程 const jwtAuth = { login: async (username, password) => { // 1. 验证用户 const user = await validateUser(username, password); // 2. 生成JWT const token = jwt.sign( { userId: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '24h' } ); // 3. 可选:存储在HttpOnly Cookie中 res.cookie('jwt', token, { httpOnly: true, secure: true, sameSite: 'strict' }); return token; }, // 验证中间件 authenticate: (req, res, next) => { // 从Cookie或Authorization头获取token const token = req.cookies.jwt || req.headers.authorization?.split(' ')[1]; if (!token) { return res.status(401).json({ error: '未授权' }); } try { const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded; next(); } catch (error) { return res.status(401).json({ error: 'Token无效' }); } } };6.2Session、JWT、OAuth 2.0 对比
Session-Based: 优点: 服务端可控,安全性高,可随时撤销 缺点: 服务器有状态,扩展性差,CSRF风险 适用: 传统Web应用,管理后台 JWT (Token-Based): 优点: 无状态,扩展性好,适合微服务 缺点: Token一旦签发无法撤销,需短有效期 适用: 移动App,SPA,API服务 OAuth 2.0: 优点: 第三方登录,权限委托,标准化 缺点: 实现复杂,依赖第三方 适用: 社交登录,API授权7.安全最佳实践
7.1Cookie 安全设置
// 安全的Cookie配置 const secureCookieConfig = { // 生产环境必须启用 secure: process.env.NODE_ENV === 'production', // 防止XSS攻击 httpOnly: true, // 防止CSRF攻击 sameSite: 'strict', // 或 'lax' // 限制作用域 domain: 'example.com', // 不设置点前缀,限制精确域名 path: '/api', // 限制路径 // 过期时间 maxAge: 15 * 60 * 1000, // 15分钟(会话类Cookie) // 签名防止篡改 signed: true }; // Express中应用 res.cookie('sessionId', sessionId, secureCookieConfig);7.2防御常见攻击
// 1. 防御CSRF app.use(csrf({ cookie: { key: '_csrf', path: '/', httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict' } })); // 2. 设置安全响应头 app.use(helmet()); // 3. 限制Cookie数量 app.use((req, res, next) => { const cookieCount = req.headers.cookie?.split(';').length || 0; if (cookieCount > 10) { return res.status(400).json({ error: 'Too many cookies' }); } next(); });8.调试与测试工具
8.1浏览器开发者工具
// 查看和管理Cookie console.log(document.cookie); // Chrome DevTools中: // 1. Application → Storage → Cookies // 2. Network → 查看请求头中的Cookie // 3. 手动修改和删除Cookie进行测试8.2命令行工具
# 查看网站Cookie curl -v https://example.com # 发送带Cookie的请求 curl -H "Cookie: sessionId=abc123" https://api.example.com/data # 测试Cookie属性 # 使用浏览器插件:EditThisCookie, Cookie-Editor总结
选择建议:
传统Web应用:使用 Session + HttpOnly Cookie
SPA + API:使用 JWT + HttpOnly Cookie 或 Authorization Header
第三方集成:使用 OAuth 2.0
敏感操作:启用双重认证,Session + 短期Token
关键要点:
Cookie 自动传输:浏览器自动在请求头中携带
Session 需要Cookie:Session ID通过Cookie传递
安全第一:始终使用 HttpOnly + Secure + SameSite
跨域注意:需要正确配置CORS和Cookie属性
性能考虑:避免过多Cookie,合理设置过期时间
现代趋势:
逐渐转向无状态认证(JWT)
SameSite=Lax 成为默认
第一方Cookie隔离(Partitioned属性)
减少对第三方Cookie的依赖