跳到主要内容

Cookie 与 Session

问题

Cookie 和 Session 的区别是什么?如何实现用户登录状态管理?

面试速答版

Cookie 和 Session 是什么关系? 互补不互斥:Cookie 是载体,Session 是服务端的状态

  • HTTP 是无状态的,服务端要识别用户得靠某种「凭证」。
  • 经典做法:服务端创建 Session 存到内存/Redis → 把 sessionId 通过 Set-Cookie 写到浏览器 → 浏览器后续请求自动带上 → 服务端用 sessionId 查回 Session。
  • Cookie 存在客户端(约 4KB),每次请求自动携带;Session 存在服务端(无大小限制,但占内存)。

Cookie 有哪些重要属性? 面试爱考 4 个安全相关的:

  • HttpOnly:禁止 JS 通过 document.cookie 访问,防 XSS 偷 token
  • Secure:只在 HTTPS 下传输,防中间人嗅探。
  • SameSiteStrict 完全禁跨站、Lax(Chrome 默认)允许导航 GET、None 允许跨站但必须配 Secure主要防 CSRF
  • Domain / Path:限制 Cookie 的作用范围,子域共享要设置成父域。

用户登录状态怎么实现?方案怎么选? 两种主流方案:

  • Session + Cookie:服务端有状态,注销/踢出方便,但分布式部署要把 Session 存 Redis 共享,否则会话丢失。
  • JWT:服务端无状态,天然适配分布式,但主动注销难(要靠黑名单或短过期 + Refresh Token)。
  • 工程上推荐:单体或中小项目用 Session,大型分布式 / 多端 / 微服务用 JWT。详见 JWT 认证
  • 安全配置三件套:HttpOnly + Secure + SameSite=Lax

答案

Cookie 和 Session 都是用于维护用户状态的机制,但存储位置和工作方式不同。


基本概念

特性CookieSession
存储位置客户端(浏览器)服务端
存储大小约 4KB无限制(服务端内存)
安全性较低(可被篡改)较高
生命周期可设置过期时间默认关闭浏览器失效
跨域同域下共享不可跨服务器

// 设置 Cookie
document.cookie = 'name=value; expires=Fri, 31 Dec 2024 23:59:59 GMT; path=/; domain=.example.com; secure; samesite=strict';
属性说明示例
name=valueCookie 名称和值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
// 工具类封装
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快速、可共享、可过期需要维护生产环境

对比项CookieSession
存储位置浏览器服务器
存储容量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 // 签名防篡改
};

前端配置

// 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 // 必须
});

常见面试问题

答案

区别CookieSession
存储位置浏览器服务器
安全性低(可篡改)
存储大小4KB无限制
生命周期可设置过期默认会话级
服务器压力占用资源

工作关系:Session 通常依赖 Cookie 存储 SessionId。

答案

// 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 }),
// ...
}));

答案

防止 CSRF(跨站请求伪造)攻击。

行为场景
Strict完全禁止跨站携带银行、支付
Lax允许导航 GET 请求普通网站(默认)
None允许跨站(需 Secure)第三方服务、广告

答案

  1. 数据位置:Session 数据存在服务端,用户无法查看和修改
  2. 传输内容:只传输 SessionId,敏感数据不暴露
  3. 可控性:服务端可随时使 Session 失效
  4. 防篡改:SessionId 可加密签名
// Cookie 方式(不安全)
// 用户信息暴露在浏览器
document.cookie = 'userId=123; role=admin';
// 可被篡改

// Session 方式(安全)
// 只传输 SessionId
document.cookie = 'sessionId=abc123';
// 用户信息存在服务端,无法篡改

相关链接