☰
Midway Express Session 组件完全指南:从配置到自定义 Store 的演进与实战
2026/9/29 21:33:19 网站建设 项目流程
  • 后端
  • 微服务
  • 云原生

【免费下载链接】midway

🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 🌈

项目地址:https://gitcode.com/gh_mirrors/mi/midway
点击查看免费下载

本文围绕 Midway 框架中面向 Express 应用的会话(Session)组件@midwayjs/express-session展开,梳理该组件从 v3 到 v4 的关键演进脉络,并基于仓库源码与测试用例深入讲解其安装方式、默认配置、cookie-session 与 express-session 双引擎的切换机制,以及如何接入 memorystore、connect-redis 等自定义会话存储。读完本文,你将能够在 Midway + Express 项目中独立完成 Session 的配置、调优与存储扩展。

组件定位:Express 应用的会话能力

@midwayjs/express-session是 Midway 为 Express 场景提供的会话组件,其设计目标很明确:开箱即用、零配置即可获得安全的 Cookie 会话能力,同时保留切换到服务端存储(如 Redis、内存 Store)的完整扩展路径。

从仓库结构看,该组件核心代码非常精简,全部位于 packages/express-session/src 目录下:

  • configuration.ts:组件装配入口,注册session命名空间与默认配置,并在onReady阶段把SessionMiddleware注入所有 Express 应用;
  • middleware/session.ts:核心中间件,负责会话中间件的选择与装配;
  • store.ts:SessionStoreManager,统一管理自定义会话存储的注册与实例化;
  • config/config.default.ts:默认配置。

组件通过 index.ts 对外导出Configuration、SessionMiddleware与SessionStoreManager,使用方式与 Midway 其他组件完全一致。

安装与默认启用

安装该组件只需一条命令,同时建议安装类型声明:

$ npm i @midwayjs/express-session --save $ npm i @types/express-session --save-dev

值得注意的一个事实是:@midwayjs/express已经默认启用了该组件,因此在常规的 Express 应用中,你甚至不需要显式引入它即可获得 Session 能力。这一"默认开启"的变更正是 CHANGELOG 中 3.0.0-beta.10 版本记录的内容——"default add session & bodyparser support for koa/express/faas",即 Session 与 BodyParser 成为 Koa/Express/FaaS 三端默认能力的一部分。

从源码看,这一默认启用机制由 configuration.ts 实现:组件在onReady阶段通过MidwayApplicationManager.getApplications(['express'])拿到所有 Express 应用,并统一调用app.useMiddleware(SessionMiddleware),无需业务代码手动挂载。

需要说明的是,当前仓库中组件版本为4.2.3(见 package.json),运行环境要求 Node.js>=20,依赖cookie-session与express-session两个底层库。

默认配置详解:一行不能少的安全底线

组件默认配置定义在 config/config.default.ts,类型为SessionOptions & { enable: boolean }:

export const session: SessionOptions & { enable: boolean } = { enable: true, secret: undefined, // must be set in application name: 'MW_SESS', resave: true, saveUninitialized: true, cookie: { maxAge: 24 * 3600 * 1000, // ms httpOnly: true, // sameSite: null, }, };

各配置项的作用如下:

配置项默认值说明
enabletrue会话功能总开关,设为false时中间件直接不生效
secretundefined签名密钥,必须在应用配置中显式设置,否则启动会报错
nameMW_SESS存放会话 ID 的 Cookie 名称
resavetrue会话在请求期间未被修改时是否强制重新保存(仅 express-session 引擎使用)
saveUninitializedtrue是否为未初始化的新会话下发 Cookie(仅 express-session 引擎使用)
cookie.maxAge24 * 3600 * 1000Cookie 有效期,单位为毫秒,默认 24 小时
cookie.httpOnlytrue禁止客户端 JavaScript 读取 Cookie

其中secret是唯一没有默认值、必须在业务侧提供的配置。从 middleware/session.ts 的源码可以看到解析顺序:

const secret = this.sessionConfig.secret ?? this.configService.getConfiguration('express.keys') ?? this.configService.getConfiguration('keys'); if (!secret) { throw new MidwayConfigMissingError('config.session.secret'); } this.sessionConfig.secret = [].concat(secret);

即依次回退到config.session.secret→config.express.keys→ 顶层config.keys,三者都未配置时抛出MidwayConfigMissingError('config.session.secret')。这与 CHANGELOG 中 3.0.1 记录的一次 Bug Fix——"config key required"——相呼应:该修复强化了密钥缺失时的校验行为。

此外,源码还内置了一条安全警告:若显式把cookie.httpOnly设置为false,中间件会通过 Logger 输出告警,提示"会话数据可被客户端 JavaScript 读取非常危险",建议保持默认的true。

双引擎机制:Cookie 会话与 Store 会话的自动切换

这是该组件最值得理解的设计:默认使用 cookie-session 将整个会话数据写入客户端 Cookie,而当配置了自定义 Store 时,自动切换为 express-session 的服务端会话模式。这一策略在 README 中有明确说明,并在 middleware/session.ts 中得到印证:

const store = this.sessionStoreManager.getSessionStore(session); if (store) { this.sessionConfig.store = store; } if (!this.sessionConfig.store) { return cookieSession( Object.assign(this.sessionConfig.cookie, { keys: this.sessionConfig.secret, name: this.sessionConfig.name, }) ); } else { return session(this.sessionConfig); }

对应关系如下:

  • 未配置任何 Store→ 使用cookie-session:会话数据整体存放在 Cookie 中,服务端无状态,适合轻量场景;
  • 配置了 Store(如 memorystore、connect-redis)→ 使用express-session:Cookie 中仅存放会话 ID,数据保存在服务端存储中,适合多实例与高安全场景。

注意getSessionStore(session)传入的session正是express-session库本身——这是为了兼容 express-session 生态中"Store 工厂函数接收 session 对象"的约定(见下文)。

测试用例 test/index.test.ts 对两种模式均有覆盖:cookie-sessionfixture 验证会话写入后set-cookie响应头包含MW_SESS=...;memory-sessionfixture 验证配置 Store 后的写入行为。

自定义 Session Store:memorystore 与 connect-redis

通过 SessionStoreManager 注册 Store

自定义 Store 的入口是组件导出的SessionStoreManager(单例 Bean,见 store.ts),在Configuration的onReady生命周期中调用setSessionStore(storeClz, options)完成注册。

以memorystore为例(完整示例见 README.md):

import { Configuration, Inject } from '@midwayjs/core'; import * as session from '@midwayjs/express-session'; import MemoryStore = require('memorystore'); @Configuration({ imports: [express, session], }) export class AutoConfiguration { @Inject() sessionStoreManager: session.SessionStoreManager; async onReady() { this.sessionStoreManager.setSessionStore(MemoryStore, { checkPeriod: 86400000, // 每 24 小时清理一次过期会话 }); } }

memorystore的checkPeriod参数控制过期条目清理周期(毫秒)。在仓库的 memory-session fixture 中,还展示了配套的onStop清理逻辑:应用停止时取出 Store 并调用store.stopInterval()停止定时清理任务,避免进程残留定时器。

接入 Redis:connect-redis 示例

如需在分布式部署中共享会话,可接入connect-redis(完整示例同样见 README.md):

import { Configuration, Inject } from '@midwayjs/core'; import * as session from '@midwayjs/express-session'; import RedisStore from "connect-redis"; import { createClient } from "redis"; // 初始化客户端 let redisClient = createClient(); redisClient.connect().catch(console.error); @Configuration({ imports: [express, session], }) export class AutoConfiguration { @Inject() sessionStoreManager: session.SessionStoreManager; async onReady() { // 初始化 store this.sessionStoreManager.setSessionStore(RedisStore, { client: redisClient, prefix: "myapp:", // ... }); } }

prefix用于给 Redis 中的会话键添加命名空间,避免与其他应用的数据冲突。除上述两个示例外,express-session 生态中所有兼容的 Session Store 均可按同样方式接入。

Store 实例化的三种形态

SessionStoreManager的内部实现(store.ts)支持三种注册形态,这在测试用例 test/index.test.ts 中被逐一验证:

  1. 类(Class):如MemoryStore,直接new (StoreClz)(options)实例化;
  2. 工厂函数:接收express-session对象、返回 Store 类(express-session 生态的 Store 扩展规范),即new (StoreClz(session))(options);
  3. 已实例化的 Store 对象:直接原样复用,getSessionStore()返回的就是传入的那个实例。
@Provide() @Scope(ScopeEnum.Singleton) export class SessionStoreManager { private sessionStoreClz; private sessionStore; private sessionStoreOptions: any; setSessionStore(sessionStore, options = {}) { ... } getSessionStore(session?) { // 根据类型分别走 new Class / new Factory(session) / 直接复用 } }

该 Manager 是单例(ScopeEnum.Singleton),保证整个应用生命周期内只有一个 Store 实例被复用。另外值得注意的是:自定义 Store 只有在express-session引擎下才会真正生效,这正对应了 CHANGELOG 中 3.0.0-beta.14 记录的依赖升级——cookie-session升级到 v2。

控制器中的会话读写:基于测试的行为参考

组件对开发者的暴露面是 Express 的req.session。仓库 cookie-session fixture 中的控制器示例清晰地展示了四种典型操作:

@Controller('/') export class HomeController { @Get('/get') async get(req) { return req.session; // 读取整个会话 } @Get('/set') async set(req) { req.session = req.query; // 整体赋值会话 return req.session; } @Get('/setKey') async setKey(req) { req.session.key = req.query.key; // 修改单个键 return req.session; } @Get('/remove') async remove(req) { req.session = null; // 置空即销毁会话 return req.session; } }

对应测试 test/index.test.ts 验证了以下关键行为:

  • 写入:GET /set?foo=bar返回{ foo: 'bar' }并下发MW_SESS=...的set-cookie;
  • 惰性下发:会话未被写入时(如只访问/get),响应中不会出现MW_SESSCookie——避免为无状态请求徒增流量;
  • 键级修改:req.session.key = value只更新单个字段,其余字段保留(测试中先写入key=foo, foo=bar,再只改key=bar,结果仍包含foo=bar);
  • 销毁:req.session = null后响应返回MW_SESS=;(空 Cookie 清除指令),再次读取会话即为空。

此外,samesite-none-session fixture 与对应测试演示了跨站场景(如嵌入第三方站点时)如何通过配置使 Cookie 携带samesite=none——测试中以 Chrome 81 的 User-Agent 与x-forwarded-proto: https模拟跨站安全上下文,断言响应头包含; samesite=none;。

版本演进:从 CHANGELOG 看组件关键节点

CHANGELOG.md 记录了该组件自 v3.0.0 起的演进历史(仓库当前为 v4.2.3)。绝大多数版本是随 Monorepo 发布的纯版本同步("Version bump only"),真正对组件行为产生影响的关键节点可归纳如下:

版本类型变更内容
3.0.0-beta.10Feature默认集成 Session 与 BodyParser 支持,Koa/Express/FaaS 三端默认启用
3.0.0-beta.12Feature支持抛出带 HTTP 状态码的错误(throw err status)
3.0.0-beta.14Bug Fix / depscookie-session依赖升级至 v2,同时修正配置类型定义
3.0.1Bug Fix修复config key required,强化密钥缺失校验
3.0.2Bug Fix修复单例调用下请求作用域失效的问题(singleton invoke request scope not valid)
3.2.1Bug FixSwagger UI 的 JSON 路径替换(随主仓库修复同步)
3.6.0Feature新增 guard 能力(随主仓库 v3.6.0 同步引入)

其中3.0.1的密钥校验与3.0.0-beta.10的默认启用,正是前文源码中MidwayConfigMissingError抛出逻辑与onReady自动挂载中间件机制的来源,二者在 middleware/session.ts 与 configuration.ts 中均有直接体现。

实战建议:常见配置决策

综合以上源码、README 与测试行为,给出几个实战层面的配置建议:

  1. 密钥务必独立配置:不要依赖express.keys或顶层keys的隐式回退,建议在config.*.ts中显式配置session.secret,并保证不同环境使用不同密钥;
  2. 轻量单机场景保持默认:未配置 Store 时组件自动走cookie-session,会话数据全部在客户端,服务端无状态,适合单体或 Session 数据量小的场景;
  3. 多实例/安全敏感场景选 Store:接入connect-redis等共享存储后自动切换为express-session引擎,Cookie 只存会话 ID,并可配合prefix隔离多应用数据;
  4. 善用enable开关:对纯 API、无状态服务,可将session.enable设为false完全关闭会话,减少不必要的 Cookie 流量与中间件开销;
  5. 注意httpOnly与sameSite:保持httpOnly: true(源码有显式告警),跨站嵌入场景按需配置sameSite: none并确保 HTTPS 安全上下文;
  6. 会话生命周期管理:在onStop中显式停止 Store 的清理定时器(如memorystore的stopInterval()),避免应用退出时残留后台任务。

如需继续深入,可直接阅读该组件的 源码目录、测试用例 以及 README,以上全部实现细节均可在仓库中直接验证。

  • 后端
  • 微服务
  • 云原生

【免费下载链接】midway

🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 🌈

项目地址:https://gitcode.com/gh_mirrors/mi/midway
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询