破解 Mongoose:我如何构建全局插件来阻止数据泄漏 🛡️
Source: Dev.to
架构问题
在典型的 Express/Mongoose 应用中,安全性通常在 Controller 层处理:
// Controller
const user = await User.findById(req.params.id);
const safeUser = omit(user.toObject(), ['password', 'ssn']); // 手动过滤
res.json(safeUser);
这很容易出现人为错误。如果你在后台任务、脚本或其他控制器中访问数据库,就必须记得使用相同的过滤。
安全应该在数据定义的地方定义:在 Schema 中。
进入 FieldShield
FieldShield 是一个原生的 Mongoose 插件,允许你在 schema 定义中直接为每个字段指定访问角色。
const UserSchema = new Schema({
username: { type: String, shield: { roles: ['public'] } },
email: { type: String, shield: { roles: ['owner', 'admin'] } },
apiKey: { type: String, shield: { roles: ['admin'] } },
password: { type: String, shield: { roles: [] } } // 对所有人隐藏
});
实现细节:pre('find') Hook
魔法发生在我们拦截 Mongoose 查询的方式上。安装 FieldShield 后,我们会修改 Mongoose Query 原型,使其接受上下文(角色)。
随后,我们注册一个全局的 pre 钩子,在查询发送到 MongoDB 之前检查它。
// Simplified logic from src/query.ts
schema.pre('find', function() {
const roles = this._shieldRoles; // 通过 .role('admin') 传入
const allowedFields = calculateAllowedFields(modelName, roles);
// 强制在查询上使用投影
this.select(allowedFields);
});
这意味着数据库 只返回你被允许看到的字段。敏感数据甚至不会进入你的 Node.js 进程内存。
挑战:聚合管道
简单查询很容易。但 Model.aggregate() 呢?聚合允许任意阶段重塑文档,导致难以追踪字段。
FieldShield 通过动态注入 $project 阶段来解决。我们分析你的管道,并在初始 $match 之后插入保护阶段,确保在使用索引的同时,在数据流入管道的其余阶段(例如 $group 或 $lookup)之前完成过滤。
// Input
await User.aggregate([
{ $match: { status: 'active' } },
// ... more stages
]).role('public');
// Actual Pipeline Executed
[
{ $match: { status: 'active' } },
{ $project: { username: 1, _id: 1 } }, // 由 FieldShield 注入
// ... more stages
]
v2.2 新特性:递归 Shield 继承 🔄
v2.2 解决的一个棘手技术挑战是 嵌套对象和数组的继承。
在 MongoDB 中,preferences.theme 与 preferences 是不同的路径。如果你隐藏 preferences.notifications,用户还能看到 preferences 对象吗?
我们实现了一个递归解析器,根据子字段的可见性合成父字段的权限:
- 只要 任意 子字段可见,父字段即可见。
- 当 所有 子字段都隐藏时,父字段隐藏。
- 父字段继承所有子字段角色的 并集。
这让你无需在父对象上手动复制 shield 配置。
性能验证 🚀
因为 FieldShield 使用原生 MongoDB 投影,它实际上比获取完整文档后在 JavaScript 中过滤更 快。
- 网络 I/O: 减少(负载更小)。
- 内存: 减少(创建的对象更少)。
- CPU: 减少(过滤由 MongoDB 的 C++ 实现完成)。
我们需要你! 🫵
FieldShield 完全开源,我们对 v3.0 有宏大计划,包括:
- 🛡️ 高级通配符策略
- 🔍 GraphQL 集成
- ⚡ 策略计算缓存
我们正在寻找贡献者!无论你是 TypeScript 大师、Mongoose 专家,还是想写更好的文档,都欢迎加入。
适合新人上手的 Issue
- 为边缘情况添加更多单元测试
- 改进文档示例
- 创建基准测试套件
给仓库加星并查看 issue:
👉