- 后端
- 微服务
- 云原生
【免费下载链接】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. 🌈
本文围绕 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, }, };各配置项的作用如下:
| 配置项 | 默认值 | 说明 |
|---|---|---|
enable | true | 会话功能总开关,设为false时中间件直接不生效 |
secret | undefined | 签名密钥,必须在应用配置中显式设置,否则启动会报错 |
name | MW_SESS | 存放会话 ID 的 Cookie 名称 |
resave | true | 会话在请求期间未被修改时是否强制重新保存(仅 express-session 引擎使用) |
saveUninitialized | true | 是否为未初始化的新会话下发 Cookie(仅 express-session 引擎使用) |
cookie.maxAge | 24 * 3600 * 1000 | Cookie 有效期,单位为毫秒,默认 24 小时 |
cookie.httpOnly | true | 禁止客户端 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 中被逐一验证:
- 类(Class):如
MemoryStore,直接new (StoreClz)(options)实例化; - 工厂函数:接收
express-session对象、返回 Store 类(express-session 生态的 Store 扩展规范),即new (StoreClz(session))(options); - 已实例化的 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.10 | Feature | 默认集成 Session 与 BodyParser 支持,Koa/Express/FaaS 三端默认启用 |
| 3.0.0-beta.12 | Feature | 支持抛出带 HTTP 状态码的错误(throw err status) |
| 3.0.0-beta.14 | Bug Fix / deps | cookie-session依赖升级至 v2,同时修正配置类型定义 |
| 3.0.1 | Bug Fix | 修复config key required,强化密钥缺失校验 |
| 3.0.2 | Bug Fix | 修复单例调用下请求作用域失效的问题(singleton invoke request scope not valid) |
| 3.2.1 | Bug Fix | Swagger UI 的 JSON 路径替换(随主仓库修复同步) |
| 3.6.0 | Feature | 新增 guard 能力(随主仓库 v3.6.0 同步引入) |
其中3.0.1的密钥校验与3.0.0-beta.10的默认启用,正是前文源码中MidwayConfigMissingError抛出逻辑与onReady自动挂载中间件机制的来源,二者在 middleware/session.ts 与 configuration.ts 中均有直接体现。
实战建议:常见配置决策
综合以上源码、README 与测试行为,给出几个实战层面的配置建议:
- 密钥务必独立配置:不要依赖
express.keys或顶层keys的隐式回退,建议在config.*.ts中显式配置session.secret,并保证不同环境使用不同密钥; - 轻量单机场景保持默认:未配置 Store 时组件自动走
cookie-session,会话数据全部在客户端,服务端无状态,适合单体或 Session 数据量小的场景; - 多实例/安全敏感场景选 Store:接入
connect-redis等共享存储后自动切换为express-session引擎,Cookie 只存会话 ID,并可配合prefix隔离多应用数据; - 善用
enable开关:对纯 API、无状态服务,可将session.enable设为false完全关闭会话,减少不必要的 Cookie 流量与中间件开销; - 注意
httpOnly与sameSite:保持httpOnly: true(源码有显式告警),跨站嵌入场景按需配置sameSite: none并确保 HTTPS 安全上下文; - 会话生命周期管理:在
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. 🌈
相关推荐
Midway Session 组件完全指南:从 Cookie Session 到自定义 Session Store 的落地实践
Midway Session 组件完全指南:从 Cookie Session 到自定义 Session Store 的落地实践 本文以 @midwayjs/se
后端微服务云原生Midway 框架 Express Session 组件使用指南:从 Cookie 会话到自定义 Store 全解析
Midway 框架 Express Session 组件使用指南:从 Cookie 会话到自定义 Store 全解析 导读 @midwayjs/express
后端微服务云原生Midway 安全组件 @midwayjs/security 全解析:从演进历史到配置实战
Midway 安全组件 @midwayjs/security 全解析:从演进历史到配置实战 @midwayjs/security 是 Midway 框架内置的通
后端微服务云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考