生产环境必须禁用应用账号的root角色,因其赋予实例级权限,可跨库操作、删系统集合、绕过本地认证限制;应为单库精确授权readWrite等角色并显式指定db字段,禁用listDatabases需结合--setParameter或自定义角色。

为什么 root 角色在生产环境必须禁用
直接给应用账号 root 角色等于敞开整个 MongoDB 实例的数据库大门,哪怕只连一个库,也能通过 use otherdb 切换并执行任意操作。MongoDB 的权限模型是「实例级」而非「连接串级」——认证凭据一旦通过,后续所有 use 切库行为都不再二次鉴权。
- 典型误操作:
db.createUser({user:"app", pwd:"x", roles:["root"]})—— 这会让该用户能删掉admin库里的系统集合 -
root角色隐式包含__system权限,可绕过enableLocalhostAuthBypass限制,甚至影响副本集仲裁逻辑 - 云托管服务(如 Atlas、阿里云 MongoDB)虽限制部分系统命令,但
root仍可能导出/重置其他租户数据(取决于底层隔离强度)
如何为单个数据库精确授权 readWrite 权限
最常用却最容易写错的是角色作用域。MongoDB 要求角色必须显式声明作用数据库,不能靠连接时的 authSource 或默认库自动推断。
- 正确做法:在
admin库中运行db.createUser({user:"app_user", pwd:"p", roles:[{role:"readWrite", db:"myapp"}]}) - 错误写法:
roles:["readWrite"]—— 这会把权限绑定到当前认证库(通常是admin),而非业务库myapp - 若需跨库读取(如日志库
logs),必须显式追加:{role:"read", db:"logs"},不能依赖通配符或继承 - 注意:
readWrite不包含createCollection,建新集合需额外加dbAdmin角色(且仅限目标库)
如何禁止用户执行 listDatabases 和跨库 use
MongoDB 默认开启 listDatabases 权限(只要用户有任意库的任意角色),这会导致应用能枚举出所有数据库名,暴露架构信息。关闭它需要精细控制内置角色组合。
- 先禁用默认行为:启动时加参数
--setParameter disableListDatabase=1(4.2+ 版本支持),否则仅靠角色无法屏蔽 - 自定义角色更可靠:
db.createRole({role:"appMinimal", privileges:[{resource:{db:"myapp", collection:""}, actions:["find","insert","update","remove"]}], roles:[]}) - 关键点:
resource中collection:""表示「该库下所有集合」,但db:"myapp"严格限定作用域;空roles:[]避免继承任何隐式权限 - 验证是否生效:用该用户登录后执行
db.adminCommand({listDatabases:1})应返回"not authorized",且use otherdb后无法执行任何命令
连接字符串里 authSource 和实际权限库不一致怎么办
常见陷阱是连接串写成 mongodb://u:p@h:27017/myapp?authSource=admin,以为「认证走 admin,操作就在 myapp」,但权限检查只看角色定义中的 db 字段,和 authSource 无关。
-
authSource仅决定「去哪个库查用户凭证」,不影响权限范围 - 如果用户是在
myapp库创建的(use myapp; db.createUser(...)),那它的角色默认绑定到myapp,此时authSource=myapp才匹配 - 最佳实践:所有用户统一在
admin库创建,角色中db显式指定业务库名,连接串固定用authSource=admin - 验证方法:
db.runCommand({connectionStatus:1}).authInfo.authenticatedUserRoles查看当前会话实际加载的角色及其db值
真正容易被忽略的是:MongoDB 的 show dbs 命令本质是调用 listDatabases,而很多驱动(如 PyMongo)的 list_database_names() 也会触发它。即使业务代码从不主动列库,某些 ORM 初始化或监控探针可能偷偷执行——得从参数层和角色层双重掐断。











