Skip to content

Node系列 · Express:session

Session 是传统服务端会话方案——登录后服务端存一份"会话状态",客户端只持有 sessionId。本章对比 Cookie 与 Session、Express 用 express-session 实现登录态管理。

一、Cookie vs Session

维度CookieSession
数据存储客户端服务端
容量4KB无限制(看存储后端)
安全性数据暴露在客户端数据在服务端,客户端只有 ID
实现复杂度简单需要 session 存储
服务端扩展无状态(多实例无障碍)多实例需要共享 session 存储
适用不敏感数据、会话标识登录态、用户偏好

TIP

登录态首选 Session——密码、用户信息等敏感数据不能放 Cookie。Session 的 ID 只是个随机字符串,泄露后服务端可主动失效。

二、express-session 基础

bash
npm install express-session
javascript
const 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.1 secret

用于签名 sessionId,防篡改:

javascript
// 生产环境:随机长字符串
app.use(session({
  secret: process.env.SESSION_SECRET || require('crypto').randomBytes(32).toString('hex'),
}));

3.2 resavesaveUninitialized

选项推荐说明
resavefalse请求未修改 session 时不重写存储——性能更好
saveUninitializedfalse不保存空 session——避免无意义的存储写入

完整配置:

javascript
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 在每次请求时自动续期——活跃用户不会突然过期:

javascript
app.use(session({
  rolling: true,
  cookie: { maxAge: 60 * 60 * 1000 },  // 每次请求重置 1 小时
}));

WARNING

滚动会话有安全权衡:活跃用户一直不过期,但 token 长期有效——被盗后攻击者能长期使用。需要结合 IP / User-Agent 检测。

四、session 存储后端

默认 express-session 把 session 存在内存——多实例部署会失效(每个进程一份内存)。

4.1 内存存储(默认)

javascript
// 不指定 store 就是内存存储
app.use(session({ secret: 'xxx' }));

适用:单实例开发。生产禁用——重启丢所有 session。

4.2 Redis 存储(生产首选)

bash
npm install redis connect-redis
javascript
const 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 数据库存储

bash
npm install connect-session-sequelize
javascript
const 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

javascript
// 存
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

javascript
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

登录后 regenerate sessionId 是安全最佳实践——防止"会话固定攻击"(攻击者预先设一个 sessionId 让受害者用,登录后劫持会话)。

六、退出登录

javascript
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 的取舍

维度SessionJWT
数据存储服务端客户端(payload)
多实例需要共享存储无状态,天然支持
主动失效✅(服务端删 session)❌(要等过期或拉黑名单)
实现较重
适用传统 Web 应用前后端分离 / 移动端 / 微服务

TIP

传统 Web 用 Session——后台能主动踢人(封号、改密)。API / 移动端 / 微服务用 JWT——天然无状态、跨域友好。详见 JWT 章节。

九、小结

  • Session 把数据存服务端,客户端只持 sessionId——比 Cookie 安全
  • express-session 是标准中间件;生产用 Redis 存储支持多实例
  • 关键配置:secret(签名密钥)+ resave: false + saveUninitialized: false + Cookie 安全属性
  • 登录后 regenerate sessionId 防会话固定;退出 destroy() + clearCookie()
  • 多实例部署必须用共享存储(Redis / 数据库),不能用默认内存
  • Session 与 JWT:传统 Web 用 Session,API/移动端用 JWT