Cookie 与 Session
问题
Cookie 和 Session 的区别是什么?如何实现用户登录状态管理?
答案
Cookie 和 Session 都是用于维护用户状态的机制,但存储位置和工作方式不同。
基本概念
| 特性 | Cookie | Session |
|---|---|---|
| 存储位置 | 客户端(浏览器) | 服务端 |
| 存储大小 | 约 4KB | 无限制(服务端内存) |
| 安全性 | 较低(可被篡改) | 较高 |
| 生命周期 | 可设置过期时间 | 默认关闭浏览器失效 |
| 跨域 | 同域下共享 | 不可跨服务器 |
Cookie 详解
Cookie 属性
// 设置 Cookie
document.cookie = 'name=value; expires=Fri, 31 Dec 2024 23:59:59 GMT; path=/; domain=.example.com; secure; samesite=strict';
| 属性 | 说明 | 示例 |
|---|---|---|
| name=value | Cookie 名称和值 | userId=123 |
| expires | 过期时间(绝对时间) | Fri, 31 Dec 2024 23:59:59 GMT |
| max-age | 过期时间(秒数) | 86400(1 天) |
| path | 有效路径 | /admin |
| domain | 有效域名 | .example.com |
| secure | 仅 HTTPS 传输 | 无值 |
| httpOnly | 禁止 JS 访问 | 无值 |
| sameSite | 跨站限制 | strict/lax/none |
Cookie 操作
// 工具类封装
class CookieUtil {
// 设置 Cookie
static set(
name: string,
value: string,
options: {
expires?: Date;
maxAge?: number;
path?: string;
domain?: string;
secure?: boolean;
sameSite?: 'strict' | 'lax' | 'none';
} = {}
): void {
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
if (options.expires) {
cookie += `; expires=${options.expires.toUTCString()}`;
}
if (options.maxAge !== undefined) {
cookie += `; max-age=${options.maxAge}`;
}
if (options.path) {
cookie += `; path=${options.path}`;
}
if (options.domain) {
cookie += `; domain=${options.domain}`;
}
if (options.secure) {
cookie += '; secure';
}
if (options.sameSite) {
cookie += `; samesite=${options.sameSite}`;
}
document.cookie = cookie;
}
// 获取 Cookie
static get(name: string): string | null {
const cookies = document.cookie.split('; ');
for (const cookie of cookies) {
const [key, value] = cookie.split('=');
if (decodeURIComponent(key) === name) {
return decodeURIComponent(value);
}
}
return null;
}
// 删除 Cookie
static remove(name: string, path = '/'): void {
this.set(name, '', { maxAge: -1, path });
}
}
// 使用示例
CookieUtil.set('token', 'abc123', {
maxAge: 7 * 24 * 3600, // 7 天
path: '/',
secure: true,
sameSite: 'strict'
});
const token = CookieUtil.get('token');
CookieUtil.remove('token');
SameSite 属性
// SameSite 防止 CSRF 攻击
// Strict:完全禁止跨站请求携带 Cookie
// 从其他网站点击链接到你的网站,不会携带 Cookie
Set-Cookie: session=abc; SameSite=Strict
// Lax(Chrome 默认):允许导航的 GET 请求携带
// 从其他网站点击链接可以,但表单提交、Ajax 不行
Set-Cookie: session=abc; SameSite=Lax
// None:允许跨站(必须配合 Secure)
Set-Cookie: session=abc; SameSite=None; Secure
Session 详解
工作流程
Node.js Session 实现
import express from 'express';
import session from 'express-session';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';
const app = express();
// Redis 客户端(生产环境推荐)
const redisClient = createClient({
url: 'redis://localhost:6379'
});
await redisClient.connect();
// 配置 Session
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: 'your-secret-key',
name: 'sessionId', // Cookie 名称
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 24 * 60 * 60 * 1000, // 1 天
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict'
}
}));
// 登录接口
app.post('/login', (req, res) => {
const { username, password } = req.body;
// 验证用户...
// 存储 Session
req.session.userId = 123;
req.session.username = username;
res.json({ success: true });
});
// 获取用户信息
app.get('/user', (req, res) => {
if (req.session.userId) {
res.json({
userId: req.session.userId,
username: req.session.username
});
} else {
res.status(401).json({ error: 'Unauthorized' });
}
});
// 登出
app.post('/logout', (req, res) => {
req.session.destroy((err) => {
res.clearCookie('sessionId');
res.json({ success: true });
});
});
Session 存储方案
| 存储方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 内存 | 快速、简单 | 重启丢失、不能共享 | 开发环境 |
| 文件 | 简单 | 性能差、不能共享 | 小型应用 |
| 数据库 | 持久化、可查询 | 性能一般 | 需要分析 |
| Redis | 快速、可共享、可过期 | 需要维护 | 生产环境 |
Cookie vs Session 对比
| 对比项 | Cookie | Session |
|---|---|---|
| 存储位置 | 浏览器 | 服务器 |
| 存储容量 | 4KB | 无限制 |
| 安全性 | 低 | 高 |
| 服务器压力 | 无 | 占用内存 |
| 跨服务器 | 可通过域名共享 | 需要共享存储 |
| 适用场景 | 偏好设置、广告追踪 | 登录状态、购物车 |
安全最佳实践
HttpOnly 防止 XSS
// 服务端设置
res.cookie('sessionId', 'abc123', {
httpOnly: true // JavaScript 无法访问
});
// 攻击者无法通过 document.cookie 获取
Secure 防止中间人攻击
// 仅 HTTPS 传输
res.cookie('sessionId', 'abc123', {
secure: true
});
SameSite 防止 CSRF
// 限制跨站请求携带 Cookie
res.cookie('sessionId', 'abc123', {
sameSite: 'strict'
});
完整安全配置
// 生产环境 Cookie 配置
const cookieOptions = {
httpOnly: true, // 防止 XSS
secure: true, // 仅 HTTPS
sameSite: 'strict', // 防止 CSRF
maxAge: 3600000, // 1 小时
signed: true // 签名防篡改
};
跨域 Cookie
前端配置
// fetch 携带 Cookie
fetch('https://api.example.com/user', {
credentials: 'include' // 关键配置
});
// axios 配置
axios.defaults.withCredentials = true;
后端配置
// Express CORS 配置
import cors from 'cors';
app.use(cors({
origin: 'https://www.example.com', // 不能使用 *
credentials: true
}));
// Cookie 跨域设置
res.cookie('token', 'abc', {
sameSite: 'none', // 必须
secure: true // 必须
});
常见面试问题
Q1: Cookie 和 Session 的区别?
答案:
| 区别 | Cookie | Session |
|---|---|---|
| 存储位置 | 浏览器 | 服务器 |
| 安全性 | 低(可篡改) | 高 |
| 存储大小 | 4KB | 无限制 |
| 生命周期 | 可设置过期 | 默认会话级 |
| 服务器压力 | 无 | 占用资源 |
工作关系:Session 通常依赖 Cookie 存储 SessionId。
Q2: 如何防止 Cookie 被盗取?
答案:
// 1. HttpOnly - 防止 XSS 窃取
Set-Cookie: sessionId=abc; HttpOnly
// 2. Secure - 仅 HTTPS 传输
Set-Cookie: sessionId=abc; Secure
// 3. SameSite - 防止 CSRF
Set-Cookie: sessionId=abc; SameSite=Strict
// 4. 签名 - 防止篡改
// 使用 cookie-signature 库签名
// 5. 定期更换 SessionId
// 登录后重新生成
Q3: 分布式 Session 如何处理?
答案:
推荐方案:Redis 集中存储
// 所有服务器连接同一个 Redis
import RedisStore from 'connect-redis';
app.use(session({
store: new RedisStore({ client: redisClient }),
// ...
}));
Q4: Cookie 的 SameSite 属性有什么用?
答案:
防止 CSRF(跨站请求伪造)攻击。
| 值 | 行为 | 场景 |
|---|---|---|
| Strict | 完全禁止跨站携带 | 银行、支付 |
| Lax | 允许导航 GET 请求 | 普通网站(默认) |
| None | 允许跨站(需 Secure) | 第三方服务、广告 |
Q5: 为什么 Session 比 Cookie 安全?
答案:
- 数据位置:Session 数据存在服务端,用户无法查看和修改
- 传输内容:只传输 SessionId,敏感数据不暴露
- 可控性:服务端可随时使 Session 失效
- 防篡改:SessionId 可加密签名
// Cookie 方式(不安全)
// 用户信息暴露在浏览器
document.cookie = 'userId=123; role=admin';
// 可被篡改
// Session 方式(安全)
// 只传输 SessionId
document.cookie = 'sessionId=abc123';
// 用户信息存在服务端,无法篡改