资讯动态

MongoDB权限控制:从数据库级别到集合级别,彻底搞定用户角色配置

发布时间:2026/10/13 4:22:57 来源:尧图企业网站定制
你有没有遇到过这种情况接手一套MongoDB实例打开权限列表一看几十个用户全顶着readWrite满天飞谁都能建表、谁都能删集合问就是“当时图省事”。省事一时爽事后火葬场。等到哪天有人手滑把开发库的orders集合整个drop掉再后悔权限没控细就晚了。我自己在好几个项目里都吃过访问控制粒度的亏所以这期把MongoDB的数据库级别访问控制和集合级别访问控制彻底捋一遍这两级权限到底管什么、怎么配、踩过哪些坑。无论你是刚开始接触MongoDB的新手还是已经在生产环境维护了一段时间的DBA这篇文章都能让你少走弯路。1. 权限控不住库等于给所有业务开了一扇后门先别急着敲命令搞清楚MongoDB的权限模型是怎么设计的比背命令重要得多。1.1 权限三要素资源、动作、角色MongoDB里所有的权限控制底层都可以拆成三个东西资源Resource、动作Action、角色Role。资源你要管住的对象可以是一整个数据库也可以是某个库里的某一个集合collection甚至可以是整个集群。动作允许在这个资源上干什么。比如find是查数据insert是插数据update是改数据createCollection是建集合。角色一组“资源动作”的打包组合。系统自带的叫内置角色你自己拼的叫自定义角色。打个比方资源是你的房子动作是钥匙能开的门角色是一串钥匙环。你给别人一把写着“只进客厅”的钥匙他就只能进客厅你图省事递给他一整串通用钥匙全屋都能进。很多权限事故就是“递了整串钥匙”还觉得自己挺安全。1.2 数据库级别和集合级别到底差在哪这两级权限的本质区别就是授权范围的大小。数据库级别你授权时指定的是整个数据库比如给某用户readWrite权限他就能读、能写这个库里的所有集合。好处是省事坏处是一旦库里有一两张敏感表比如users、orders他也能全看全写。集合级别授权时把范围缩到某个具体的集合比如只允许某用户读shop_db里的orders集合其他集合一概碰不了。好处是粒度细坏处是配置和运维成本高。生产环境通常不是二选一而是混着用。下面这两节我们分别把这两级权限的操作细节讲透。2. 数据库级别访问控制用户归属与内置角色的作用范围说完基础来看实际操作里最容易搞混的部分。2.1 用户是全局账号角色才是“辖区”MongoDB有个特别容易让人懵的点用户本身是全局存在的定义在admin库里但角色可以绑定到任何一个数据库。你创建一个用户不意味着他只属于某一个库真正限定他活动范围的是后面跟着的roles数组。看这段命令use admin db.createUser({ user: shop_app, pwd: AppOnlyPass_2024, roles: [ { role: readWrite, db: shop_db } ] })这个用户的“认证数据库”是admin因为我们在admin库下调用的createUser。但他拥有的readWrite权限只作用于shop_db。登录时连接串里的authSource要写adminmongosh mongodb://shop_app:AppOnlyPass_2024127.0.0.1:27017/shop_db?authSourceadmin如果你在use shop_db之后创建用户认证数据库就变成了shop_db连接时的authSource也要相应改成shop_db。这个细节我见过太多人栽跟头——用户明明创建成功了客户端死活连不上最后发现是authSource写错。2.2 内置角色的杀伤半径你选对了吗数据库级别操作最常用的内置角色有这几个内置角色作用范围能做什么典型误用read指定库只读所有非系统集合以为能保护敏感字段其实整库都能看readWrite指定库读写所有非系统集合和read混用导致普通账号也能写dbAdmin指定库管理索引、统计、schema校验等给业务账号配了这个等于开了半个后门userAdmin指定库管理用户和角色只应该给DBA用dbOwner指定库readWritedbAdminuserAdmin业务账号几乎不该用这个clusterAdmin集群管理分片、副本集、集群监控千万别给应用连数据库的账号配很多人以为“库级别授权”只是选一个角色挂上去就行实际上角色的杀伤半径差别巨大。我见过最离谱的一次是给一个只做报表查询的账号配了dbOwner因为他觉得“反正只是这个库”。问题是dbOwner里包含userAdmin意味着这个账号可以继续创建新的高权限用户相当于权限自己会繁殖。实操建议应用账号尽量只用read或readWrite最多加一个具体的自定义角色DBA专用账号才考虑dbAdmin和userAdmin。凡是需要建索引、查统计信息的用dbAdmin而不是dbOwner。2.3 如何验证库级权限是否生效授权完别急着走先验证。mongosh连上去之后执行db.runCommand({ connectionStatus: 1 })返回结果里的inheritedPrivileges字段会列出当前用户实际拥有的所有“资源动作”组合。你可以自己核对一下是不是只有指定库的读写权限而不是别的库。想测试访问别的库也行直接切过去尝试查询use salary_db db.employee_info.find()如果返回not authorized说明库级隔离生效了。3. 集合级别访问控制把权限精确到一张表数据库级别粒度太粗接下来进入本文的重头戏集合级别。3.1 什么时候必须上集合级别权限我总结了三类典型场景报表账号只想让它读orders、读inventory但不想让它碰users因为里面有手机号和地址。跨团队数据共享A团队维护整个业务库B团队只被允许读其中一两张公开表不能看其他表。多租户或微服务拆分不够干净的库一个库里堆了好几个模块的表各自只应该访问自己那一部分。这时候再给readWrite等于把整库数据送出去。集合级别是唯一合理的选择。3.2 自定义角色中的resource定义方式集合级权限没有现成的内置角色必须自己创建角色。核心是resource的写法use admin db.createRole({ role: order_readonly, privileges: [ // 精确到单张集合 { resource: { db: shop_db, collection: orders }, actions: [ find, collStats ] } ], roles: [] })resource的几种写法资源定义含义{ db: shop_db, collection: orders }精确到shop_db里的orders集合{ db: shop_db, collection: }shop_db里所有非系统集合相当于库级{ db: shop_db, collection: system.* }匹配系统集合一般不要用{ anyResource: true }所有数据库所有集合全权限标志注意这里有个容易误读的点collection: 不是匹配名为空的集合而是“库内全部集合”。想精确到单表collection字段必须写具体的集合名。3.3 action怎么选从只读到聚合写出的边界集合级授权的另一大关键在选择actions。很多人只记得find和insert结果用户跑个聚合就报错。常用actions大致分这几类数据查询类find、aggregate数据写类insert、update、remove集合结构类createCollection、dropCollection、createIndex、dropIndex统计元数据类collStats、listCollections、listIndexes一个只读账号我给的最少集合是actions: [find, aggregate, collStats]但这里有个坑很多客户端驱动在连库时会自动调用listCollections来获取集合列表如果这个动作没给mongosh/驱动可能一切正常某些图形化客户端却会报unauthorized。如果你们团队用图形化管理工具比较多建议在库资源上加一个listCollections权限db.createRole({ role: order_readonly_with_meta, privileges: [ { resource: { db: shop_db, collection: orders }, actions: [ find, aggregate, collStats ] }, { resource: { db: shop_db, collection: }, actions: [ listCollections ] } ], roles: [] })这是“集合级权限库级元数据权限”的混合方案。严格来说listCollections是库级动作但它不暴露数据内容只是集合名单所以这样混配是安全的。3.4 集合级用户的创建与身份绑定角色创建好了接下来创建用户把角色挂上去use admin db.createUser({ user: order_viewer, pwd: ReadOnly_2024!, roles: [ { role: order_readonly, db: admin } ] })因为角色创建在admin库所以绑定时的db也要写admin。连接用mongosh mongodb://order_viewer:ReadOnly_2024!127.0.0.1:27017/shop_db?authSourceadmin登录以后做个验证use shop_db db.orders.find().limit(1) // 正常返回 db.users.find() // 报错: not authorized on shop_db to execute command find db.orders.insert({ test: 1 }) // 报错: not authorized能读orders不能读users不能写orders这套集合级权限就算配成了。4. 一个完整示例从零搭建订单库的库表两级授权环境这一节我们把前面的知识点串起来完整走一遍流程。场景是这样的某公司的订单系统MongoDB里有三个库分别是shop_db业务主库、log_db日志库、backup_db备份库。现在要设计一套授权方案DBA能管所有库包括创建用户、查看集群状态。应用服务读写shop_db全部集合不能碰log_db和backup_db。报表查询只能读shop_db里的orders集合连inventory都不能看只能读数据不能写。日志服务只写log_db不读其他任何数据。4.1 开启认证前先想清楚的三件事动手前先规划别上来就开auth。第一第一个必须创建的用户是超级管理员。MongoDB有个“localhost异常豁免”在没有任何用户时本地连接可以免认证登录但一旦创建第一个用户后这个豁免就只对新创建用户的场景部分生效。所以正确的启动顺序是先用无认证模式启动立刻创建root用户再重启开启认证模式。第二绑定IP要收敛。权限配得再好如果bindIp是0.0.0.0且防火墙没关还是等于裸奔。生产环境bindIp建议只写内网IP或127.0.0.1不要把实例暴露到公网。第三密码策略别偷懒。下面所有示例密码都是演示用的真实环境请用至少16位包含大小写字母、数字和特殊字符不要拿MAC地址或者生日当密码。4.2 开启认证与创建管理员先在工作目录创建配置文件mongod.confstorage: dbPath: /data/mongo net: bindIp: 127.0.0.1 port: 27017 security: authorization: enabled先用无认证模式启动一次mongod --config /etc/mongod.conf --bind_ip 127.0.0.1然后立刻创建管理员use admin db.createUser({ user: root_admin, pwd: Root_Admin_2024#S3cure, roles: [ { role: root, db: admin } ] })重启mongod之后所有连接都必须认证。4.3 创建应用账号数据库级别授权用管理员身份登录mongosh mongodb://root_admin:密码127.0.0.1:27017/admin?authSourceadmin创建应用账号use admin db.createUser({ user: app_service, pwd: App_Svc_2024#Pass, roles: [ { role: readWrite, db: shop_db } ] })这个账号就是典型的数据库级别授权。它能在shop_db里建集合、读写所有表但连log_db、backup_db的边都摸不到。从命名上就能看出给应用服务的账号只给库级角色不给集群权限。4.4 创建报表账号集合级别授权创建自定义角色use admin db.createRole({ role: orders_ro, privileges: [ { resource: { db: shop_db, collection: orders }, actions: [ find, aggregate, collStats ] }, { resource: { db: shop_db, collection: }, actions: [ listCollections ] } ], roles: [] })然后创建报表用户并绑定角色db.createUser({ user: report_viewer, pwd: Report_2024#View, roles: [ { role: orders_ro, db: admin } ] })4.5 创建日志账号最小写权限日志服务只需要写不需要读自己的历史数据db.createRole({ role: log_writer, privileges: [ { resource: { db: log_db, collection: log_messages }, actions: [ insert, collStats ] } ], roles: [] }) db.createUser({ user: log_service, pwd: Log_Svc_2024#Write, roles: [ { role: log_writer, db: admin } ] })这里只给了insert和collStats连update都没有防止日志服务误改历史日志。这在实际中是真的会发生的——某个版本的日志处理程序有bug结果把旧日志全给更新了权限收住之后风险大幅降低。4.6 逐一验证三个账号用三种身份分别登录测试# 应用账号 mongosh mongodb://app_service:密码127.0.0.1:27017/shop_db?authSourceadmindb.orders.insert({ no: 1 }) db.orders.findOne() use log_db db.log_messages.find() // 应该报 unauthorized# 报表账号 mongosh mongodb://report_viewer:密码127.0.0.1:27017/shop_db?authSourceadmindb.orders.find().limit(1) // 成功 db.inventory.find() // 失败 db.orders.insert({ bad: 1 }) // 失败# 日志账号 mongosh mongodb://log_service:密码127.0.0.1:27017/log_db?authSourceadmindb.log_messages.insert({ msg: hello }) // 成功 db.log_messages.findOne() // 失败因为只给了insert三个账号各有边界互不越权这套环境才算真正配完。5. 我在生产环境踩过的权限坑排查思路与规避方案最后一部分专门讲坑。这些坑如果你没踩过看的时候可能会觉得“这么简单也能错”——但现实就是越简单的环节越容易被忽略。5.1 重启后“权限配置失效”其实是认证开关没开有一次某团队反馈用户都创建好了权限也授了但重启MongoDB之后所有人都不需要密码就能连或者反之所有人连不上了。排查了半天最后发现启动命令里根本没带--auth配置文件里的security.authorization也注释掉了。用户和角色还在但服务端压根没启用访问控制。判断当前mongod是否启用了认证可以看日志也可以查启动参数。最直接的方式是连上实例随便执行一个需要权限的动作如果在没认证状态下能执行说明服务端没开认证。我的习惯每次改完配置重启后第一件事就是用db.runCommand({ connectionStatus: 1 })确认会话身份和权限然后用一个已知的低权限账号测试越权访问。5.2 Unauthorized报错的完整排查链路如果你碰到not authorized别慌按这个顺序排查authSource对不对用户创建在哪个库连接串里authSource就写哪个库。实在不确定先看用户的db字段。密码特殊字符编码密码里有、#、%这些字符在URL里必须做百分号编码否则客户端会把user:passhost解析错。角色名和库名写没写对角色名大小写敏感Orders_RO和orders_ro是两个不同的角色。绑定角色时db字段填的是角色所在的库不是数据所在的库。用户是否真的有这个角色查db.getUser(username)确认roles数组里到底绑了什么。服务端日志给出明确拒绝原因看mongod.log里面通常会写Unauthorized: not authorized on X to execute command Y。这一套走下来九成问题都能定位。5.3 角色继承导致的权限放大自定义角色有个roles数组用来继承其他角色。这个设计本身没问题坏就坏在有人继承的时候没细看继承对象。举个例子你想让报表用户能读orders集合于是创建了一个角色roles: []没问题。但如果你图省事直接给他绑了readWriteAnyDatabase那他什么都能干。排查权限放大问题核心还是用connectionStatusdb.runCommand({ connectionStatus: 1 })看返回的inheritedRoles和inheritedPrivileges。注意是inherited不是直接角色。权限放大的问题往往藏在继承链里面。5.4 集合级权限和聚合管道的边界集合级权限最隐蔽的坑在聚合管道里。一个用户有orders集合的find权限但跑一个带$out或$merge的聚合时db.orders.aggregate([ { $match: { status: paid } }, { $out: backup_orders } ])会直接报错因为他没有创建backup_orders集合的权限。这是符合预期的——$out在语义上是写数据不是读数据。更隐蔽的是$lookup。假设用户有orders的find权限聚合里关联了users集合db.orders.aggregate([ { $lookup: { from: users, localField: userId, foreignField: _id, as: userInfo } } ])如果用户没有users集合的find权限这个聚合也会失败。权限是按集合逐个检查的不是说你查了orders就允许聚合顺带把别的表也扫一遍。设计报表权限的时候一定要把聚合涉及的每个集合都算进资源范围内。5.5 别把认证协议和访问控制混为一谈有同学问用户密码都认证成功了为什么有些命令还是被拒。认证Authentication只是证明“你是谁”访问控制Authorization才是决定“你能干什么”。这俩是两回事。MongoDB默认的认证机制从4.0开始是SCRAM-SHA-256老一点的客户端可能只支持SCRAM-SHA-1。如果老客户端连不上可以在创建用户时指定authenticationRestrictions或使用SCRAM-SHA-1但我不建议长期混用能升级客户端还是升级客户端。另外如果公司对安全要求高可以考虑开启TLS传输加密。权限控制只解决“谁能干什么”TLS解决“数据在传输中不被截获”两者是互补关系不是替代关系。最后分享一个我自己的小技巧每次做权限调整我都会建一个“权限自检脚本”把所有的业务账号、角色、资源、动作整理出来隔一段时间就扫描一遍。一是看有没有角色被重复绑定二是看有没有用户权限悄悄被放大三是看有没有离职同事的账号还在里面躺着。MongoDB本身提供了db.system.users.find()和db.system.roles.find()配上自动化脚本可以做到权限可视化。权限这件事认真做一次不难难的是坚持在每次架构调整、人员变动后都重新检查一遍。希望这篇能帮你把数据库级别和集合级别的访问控制一次配明白。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑