Node系列 · Express:session
Session 是传统服务端会话方案——登录后服务端存一份"会话状态",客户端只持有 sessionId。本章对比 Cookie 与 Session、Express 用 express-session 实现登录态管理。
一、Cookie vs Session
| 维度 | Cookie | Session |
|---|---|---|
| 数据存储 | 客户端 | 服务端 |
| 容量 | 4KB | 无限制(看存储后端) |
| 安全性 | 数据暴露在客户端 | 数据在服务端,客户端只有 ID |
| 实现复杂度 | 简单 | 需要 session 存储 |
| 服务端扩展 | 无状态(多实例无障碍) | 多实例需要共享 session 存储 |
| 适用 | 不敏感数据、会话标识 | 登录态、用户偏好 |
::: tip
登录态首选 Session——密码、用户信息等敏感数据不能放 Cookie。Session 的 ID 只是个随机字符串,泄露后服务端可主动失效。
:::
二、express-session 基础
npminstallexpress-sessionconst session = require('express-session'); app.use(session({ secret: 'keyboard cat', // 签名密钥(必填,生产用随机字符串) resave: false, // 强制重新保存(即使未修改) saveUninitialized: false, // 强制保存空 session cookie: { httpOnly: true, // 防 XSS secure: true, // 仅 HTTPS(生产) sameSite: 'lax', // 防 CSRF maxAge: 24 * 60 * 60 * 1000, // 24 小时 }, })); // 使用 app.post('/api/login', (req, res) => { req.session.userId = user.id; // 存 session res.json({ user }); }); app.get('/api/profile', (req, res) => { if (!req.session.userId) { return res.status(401).json({ error: '未登录' }); } // ... });三、关键配置项
3.1secret
用于签名 sessionId,防篡改:
// 生产环境:随机长字符串 app.use(session({ secret: process.env.SESSION_SECRET || require('crypto').randomBytes(32).toString('hex'), }));3.2resave与saveUninitialized
| 选项 | 推荐 | 说明 |
|---|---|---|
resave | false | 请求未修改 session 时不重写存储——性能更好 |
saveUninitialized | false | 不保存空 session——避免无意义的存储写入 |
3.3cookie选项
完整配置:
cookie: { httpOnly: true, // 防 XSS secure: process.env.NODE_ENV === 'production', // 仅 HTTPS(生产) sameSite: 'lax', // 防 CSRF maxAge: 24 * 60 * 60 * 1000, // 24 毫秒单位 domain: '.example.com', // 子域共享(可选) path: '/', // 路径 }3.4 滚动会话
rolling: true让 session 在每次请求时自动续期——活跃用户不会突然过期:
app.use(session({ rolling: true, cookie: { maxAge: 60 * 60 * 1000 }, // 每次请求重置 1 小时 }));::: warning
滚动会话有安全权衡:活跃用户一直不过期,但 token 长期有效——被盗后攻击者能长期使用。需要结合 IP / User-Agent 检测。
:::
四、session 存储后端
默认express-session把 session 存在内存——多实例部署会失效(每个进程一份内存)。
4.1 内存存储(默认)
// 不指定 store 就是内存存储 app.use(session({ secret: 'xxx' }));适用:单实例开发。生产禁用——重启丢所有 session。
4.2 Redis 存储(生产首选)
npminstallredis connect-redisconst session = require('express-session'); const RedisStore = require('connect-redis').default; const { createClient } = require('redis'); const redisClient = createClient({ url: 'redis://127.0.0.1:6379' }); await redisClient.connect(); app.use(session({ store: new RedisStore({ client: redisClient }), secret: 'xxx', resave: false, saveUninitialized: false, cookie: { maxAge: 24 * 60 * 60 * 1000 }, }));Redis 优势:
- 多实例共享 session(任意 Node 进程都能读到同一份 session)
- 自动过期(Redis TTL)
- 重启不丢数据
4.3 数据库存储
npminstallconnect-session-sequelizeconst session = require('express-session'); const SequelizeStore = require('connect-session-sequelize')(session.Store); const { sequelize } = require('./models'); app.use(session({ store: new SequelizeStore({ db: sequelize }), secret: 'xxx', resave: false, saveUninitialized: false, }));会自动创建Sessions表——MySQL/PostgreSQL 都能用。
五、session 操作 API
// 存 req.session.userId = 1; req.session.cart = [1, 2, 3]; // 读 const userId = req.session.userId; // 重新生成 sessionId(防会话固定) req.session.regenerate((err) => { req.session.userId = userId; }); // 销毁 session(退出登录) req.session.destroy((err) => { res.clearCookie('connect.sid'); res.json({ ok: true }); }); // 重新加载 session(多步操作时) req.session.reload((err) => { console.log(req.session); });5.1 登录后 regenerate
app.post('/login', async (req, res) => { // 1. 验证用户 const user = await authenticate(req.body); // 2. 登录成功,重新生成 sessionId(防会话固定) req.session.regenerate((err) => { if (err) return next(err); req.session.userId = user.id; req.session.save((err) => { res.json({ user }); }); }); });::: tip
登录后regeneratesessionId 是安全最佳实践——防止"会话固定攻击"(攻击者预先设一个 sessionId 让受害者用,登录后劫持会话)。
:::
六、退出登录
app.post('/api/logout', (req, res) => { req.session.destroy((err) => { if (err) return next(err); res.clearCookie('connect.sid'); // 清除 session cookie res.json({ ok: true }); }); });七、最佳实践
| 场景 | 推荐 |
|---|---|
| 开发环境 | 默认内存存储即可 |
| 生产环境 | Redis 存储(多实例共享 + 自动过期) |
| 登录后 | req.session.regenerate()防会话固定 |
| 退出 | req.session.destroy()+res.clearCookie() |
| Cookie 配置 | httpOnly+secure(生产)+sameSite: 'lax' |
| secret | 随机长字符串,存环境变量 |
| 多实例 | 必须用 Redis 等共享存储 |
八、与 JWT 的取舍
| 维度 | Session | JWT |
|---|---|---|
| 数据存储 | 服务端 | 客户端(payload) |
| 多实例 | 需要共享存储 | 无状态,天然支持 |
| 主动失效 | ✅(服务端删 session) | ❌(要等过期或拉黑名单) |
| 实现 | 较重 | 轻 |
| 适用 | 传统 Web 应用 | 前后端分离 / 移动端 / 微服务 |
::: tip
传统 Web 用 Session——后台能主动踢人(封号、改密)。API / 移动端 / 微服务用 JWT——天然无状态、跨域友好。详见 JWT 章节。
:::
九、小结
- Session 把数据存服务端,客户端只持 sessionId——比 Cookie 安全
express-session是标准中间件;生产用 Redis 存储支持多实例- 关键配置:
secret(签名密钥)+resave: false+saveUninitialized: false+ Cookie 安全属性 - 登录后
regeneratesessionId 防会话固定;退出destroy()+clearCookie() - 多实例部署必须用共享存储(Redis / 数据库),不能用默认内存
- Session 与 JWT:传统 Web 用 Session,API/移动端用 JWT