必须在MongoDB更新操作的查询条件中使用$expr + $eq原子化校验创建者身份,如{ _id: ObjectId("..."), $expr: { $eq: ["$createdBy", "user_123"] } },确保校验与写入不可分割,杜绝竞态与绕过风险。
用
+
在更新时校验创建者身份
MongoDB 本身不提供行级权限的“自动绑定”,必须在应用层或服务端逻辑中显式校验。最直接的做法是在每次
或
时,把
(当前操作者)和文档中的
字段做比对——但不能只靠应用层 if 判断,否则绕过代码就能直连数据库篡改。必须把校验逻辑下推到查询条件里。
正确姿势是用
让 MongoDB 自己比对:
如果
不等于
,这条更新根本不会命中任何文档,返回
—— 这比在应用层抛错更安全,因为没机会执行写操作。
务必确保
字段在插入时已写入且不可伪造(比如由服务端生成,不信任客户端传入)
字段名要统一:别一会用
一会用
,否则查询条件容易写错
注意类型一致:
是字符串,
是 ObjectId,混用会导致永远不匹配
为什么不能只靠应用层 if 判断
常见错误是先
查出文档,判断
,再执行
。这有明显竞态风险:A 用户查完还没更新,B 用户已抢先改掉
字段(如果有其他高权限路径),A 的更新就会误成功。
更严重的是,这种两步走完全依赖应用逻辑完整执行;一旦中间出错、日志漏打、或有人绕过 API 直连 shell,权限就形同虚设。
所有涉及权限的写操作,校验和执行必须原子化——也就是塞进同一个
查询条件里
不要在应用层做“读-判-写”,这是典型的安全盲区
哪怕用了 ORM 或 ODM(如 Mongoose),也要确认它没帮你拆成多条命令
配合唯一索引防止伪造
如果允许客户端传
,攻击者可能提交
来冒充。必须确保该字段由服务端可信上下文注入,且数据库层面能拦住非法值。
一个低成本加固方式是建唯一复合索引,把
和某个不可变字段(比如
)绑在一起,虽不能直接阻止写入,但配合应用层可快速发现异常:
但这只是辅助手段。真正关键的是:插入时强制用服务端变量赋值,例如 Node.js 中:
永远不要把
当作用户可输入字段透传入库
如果业务真需要“代创建”,应走明确授权流程(如
token),而非开放字段
索引本身不防篡改,但它能让异常数据在后续查询或聚合中更容易暴露
聚合更新场景下怎么保持校验
当需要用
或聚合管道更新(比如递增阅读数、更新嵌套数组),仍需保证“仅限创建者”。此时
依然可用,但要注意字段路径写法:
若更新逻辑复杂(比如要基于当前时间计算新字段),建议用聚合管道,但校验部分仍得放在
中,而不是放到
阶段里——后者不参与匹配,起不到权限控制作用。
聚合更新的
参数只负责“怎么改”,不负责“能不能改”
所有权限过滤必须落在第一个参数(查询条件)里,且优先使用
避免在 pipeline 里用
模拟权限逻辑,那只是掩耳盗铃
实际最难的不是写对这一行
,而是确保整个数据生命周期里,
始终真实、不可篡改、类型稳定。从插入那一刻起,它就得被当成敏感凭证来管。
$expr$equpdateOneupdateManyuserIdcreatedBy$exprdb.posts.updateOne(
{ _id: ObjectId("..."), $expr: { $eq: ["$createdBy", "user_123"] } },
{ $set: { title: "新标题" } }
)createdBy"user_123"{ matchedCount: 0, modifiedCount: 0 }createdBycreatorIdowner"user_123"ObjectId("...")findOnedoc.createdBy === currentUser.idupdateOnecreatedByupdatecreatedBycreatedBy{ createdBy: "admin" }createdBy_iddb.posts.createIndex({ createdBy: 1, _id: 1 }, { unique: true })await db.collection("posts").insertOne({
title: input.title,
createdBy: req.user.id, // 不取 input.createdBy
createdAt: new Date()
})createdByimpersonateupdateMany$exprdb.posts.updateMany(
{ status: "draft", $expr: { $eq: ["$createdBy", "user_456"] } },
{ $set: { status: "published" } }
)filter$setpipeline$expr$cond$exprcreatedBy