mongodb无“全局/本地权限”术语,所谓全局指admin库用户配跨库角色如readwriteanydatabase,本地指业务库用户配库级角色;应禁用高危角色、用分层建模+角色复用,在admin库统一建用户和自定义角色,严格限定资源范围,并关闭localhost exception。

MongoDB 里不存在“全局权限”和“本地权限”的官方术语,所谓“全局”通常指 admin 数据库上创建的用户 + 跨库角色(如 readWriteAnyDatabase),而“本地”多指在业务库(如 myapp)中创建用户并只授该库角色(如 {role: "readWrite", db: "myapp"})。真正要解决的,是避免用 root 或 readWriteAnyDatabase 这类高危角色,又不让每个应用都绑死一个数据库——得靠分层建模+角色复用。
用 admin 数据库统一管理用户,但限制角色作用域
所有用户必须在 admin 数据库创建(这是 MongoDB 强制要求),但角色分配不能无差别给全库权限。错误做法是:
db.createUser({user:"app1", pwd:"x", roles:["root"]}) —— 直接交出整个集群控制权
正确路径是显式声明每个角色的作用数据库:
- 给应用 A:只允许读写
orders和customers库 →roles: [{role:"readWrite", db:"orders"}, {role:"readWrite", db:"customers"}] - 给 BI 工具:只读多个库 →
roles: [{role:"read", db:"analytics"}, {role:"read", db:"logs"}] - 绝不直接授予
readWriteAnyDatabase,哪怕加了restricted: true也不保险
这种写法看似啰嗦,但能被 mongosh 的权限检查器精确识别,且在 db.runCommand({connectionStatus: 1}) 中清晰看到当前会话实际生效的权限边界。
自定义角色替代内置角色,收敛权限粒度
内置角色如 readWrite 允许对整个库任意 find/insert,但真实业务常需“只能查自己部门的数据”或“只能改 status 字段”。这时必须用自定义角色:
db.createRole({role:"orderUpdater", privileges:[{resource:{db:"myapp", collection:"orders"}, actions:["find","update"]}], roles:[]})
关键点:
-
privileges必须指定collection级资源,不能只写{db:"myapp"}(那等同于readWrite) - 如果要用字段级过滤(如只让改
status),得配合查询过滤器(collMod+validation)或应用层拦截,MongoDB 原生不支持字段级权限 - 自定义角色也必须在
admin库创建,否则其他库用户无法继承
权限继承链别绕过 admin,否则角色失效
常见错误:在 myapp 库执行 db.createRole(...),再试图让 admin 用户通过 roles: [{role:"myRole", db:"myapp"}] 绑定——这会失败,报错 RoleNotFound: role myRole@myapp not found。
原因:MongoDB 只在 admin 数据库中查找角色定义。正确流程是:
- 第一步:
use admin,然后db.createRole({role:"appReader", ...}) - 第二步:仍用
use admin,db.createUser({user:"svc", roles:[{role:"appReader", db:"admin"}]}) - 第三步:连接时指定
--authenticationDatabase admin,登录后即可操作myapp.orders
这个链条不能断,断了就变成“有角色名,没角色体”,权限校验直接跳过。
生产环境必须关闭 localhost exception
默认 MongoDB 启动时,若未启用认证(--auth),允许从 localhost 直连并创建第一个用户。但一旦启用认证,这个例外机制仍存在——只要没设 disableLocalhostAuthBypass: true,任何能 SSH 到服务器的人,都能用 mongosh 本地直连并提权。
解决方案只有两个:
- 启动时加参数:
mongod --auth --setParameter disableLocalhostAuthBypass=true - 或在配置文件
/etc/mongod.conf中写:setParameter: { disableLocalhostAuthBypass: true }
不关它,等于留了一把没锁的后门钥匙——和权限模型设计再精细也没用。











