[MongoDB小技巧21]MongoDB RBAC 完全指南:从内置角色到自定义权限的生产级实践

一、MongoDB RBAC 概述

1. 什么是 RBAC?MongoDB 为什么选择 RBAC?

MongoDB 的 RBAC 体系提供了从粗粒度(数据库级)到细粒度(集合级、操作级)的完整权限控制能力。在生产环境中,我们应始终遵循以下核心原则:

  1. 最小权限:只给必要的,不多一分
  2. 角色复用:通过角色抽象权限,避免重复配置
  3. 定期审计:权限不是一次配置就结束的,需要持续审查
  4. 分层管理:应用账号、运维账号、管理账号严格分层
  5. 审计留痕:所有权限变更可追溯,满足合规要求

基于角色的访问控制(Role-Based Access Control, RBAC)是一种将权限授予角色、再将角色授予用户的访问控制模型。MongoDB 自 2.6 版本起完整引入 RBAC 体系,并在后续版本中持续增强。

MongoDB 选择 RBAC 而非 ACL(访问控制列表)或 MAC(强制访问控制),核心原因有三:

  • 可扩展性:在大型系统中,为每个用户单独配置权限(ACL 模式)会导致管理开销呈指数级增长。RBAC 通过"角色"这一中间层实现权限的复用与抽象。
  • 运维友好:角色可以在不同用户间重复使用,权限变更只需修改角色定义,无需逐个调整用户。
  • 最小权限原则的自然承载:RBAC 的角色粒度可精细到单个集合的单个操作,为实现最小权限原则提供了技术基础。

2. 核心概念模型

MongoDB 授权模型由五个核心概念构成:

概念 定义 示例
用户(User) 访问 MongoDB 的实体,在特定数据库中创建 app_user@orders
角色(Role) 权限的命名集合,在特定数据库中定义 readWriteorder_processor
权限(Privilege) 资源 + 操作的组合,是授权的最小单元 orders 库的 find 操作
资源(Resource) 权限作用的目标,可以是集群、数据库、集合等 orders.inventory 集合
操作(Action) 允许在资源上执行的动作 findinsertcreateIndex

一个关键理解 :用户 ≠ 数据库。用户虽然在某个数据库中创建(称为"认证数据库"),但通过角色可以获得对一个或多个数据库的操作权限。这意味着,一个在 orders 数据库中创建的用户,可以被授予对 products 数据库的只读权限------用户与数据库是解耦的。

3. 启用访问控制

MongoDB 默认不启用访问控制。在生产环境中,必须在启动时显式开启:

方式一:命令行参数

bash 复制代码
mongod --auth --port 27017 --dbpath /data/db

方式二:配置文件(推荐)

yaml 复制代码
security:
  authorization: enabled

启用访问控制后,所有客户端连接都必须通过身份验证。需要注意:启用 authorization 的同时也会启用内部身份验证(如副本集成员间的认证)。

4. 认证→授权→执行 完整流程

下图展示了从客户端连接到操作执行的完整权限校验链路:

关键理解 :MongoDB 的角色只授予权限,从不限制权限。如果用户拥有两个角色,具有较高访问权限的角色优先------角色之间不存在"取交集"或"权限抵消"的逻辑。例如,即使同时拥有 readreadWriteAnyDatabaseread 也不会撤销对任何数据库的写入权限。这一设计意味着:撤销权限必须通过显式 revokeRolesFromUser 完成

二、内置角色(Built-in Roles)解析

MongoDB 提供了一系列内置角色,覆盖了数据库系统中常见的权限需求。理解这些角色是设计权限体系的基础。

1. 数据库用户角色

角色 适用范围 核心能力 典型使用场景
read 单个数据库 在所有非系统集合和 system.js 集合上执行读取操作 报表系统、数据分析只读账号
readWrite 单个数据库 read + 写入能力(insert/update/delete) 应用程序的标准操作账号

read 角色不提供对 system.namespaces 等系统集合的直接访问。

2. 数据库管理角色

角色 适用范围 核心能力 典型使用场景
dbAdmin 单个数据库 Schema 相关操作:创建索引、查看 system.profile 数据库日常维护
userAdmin 单个数据库 在当前数据库上创建和修改角色和用户 数据库级用户管理员
dbOwner 单个数据库 组合了 readWrite + dbAdmin + userAdmin 的全部权限 数据库负责人

特别注意userAdmin 允许用户向任何用户(包括自己)授予任何权限,因此间接提供了对数据库的超级用户访问权限。使用此角色需格外谨慎。

3. 集群管理角色(仅限 admin 数据库)

角色 核心能力
clusterAdmin 组合了 clusterManager + clusterMonitor + hostManager 的全部权限
clusterManager 分片和副本集的管理操作(添加/删除节点、分片等)
clusterMonitor 监控工具使用的只读权限,可监控分片和副本集集群
hostManager 服务器状态和操作日志管理

4. 备份与恢复角色(仅限 admin 数据库)

角色 核心能力
backup 执行备份操作所需的权限(mongodump 等)
restore 执行恢复操作所需的权限(mongorestore 等)

5. 跨数据库角色(仅限 admin 数据库)

角色 核心能力
readAnyDatabase 对所有数据库的只读权限
readWriteAnyDatabase 对所有数据库的读写权限
userAdminAnyDatabase 在所有数据库上管理用户和角色的权限
dbAdminAnyDatabase 对所有数据库的管理权限

6. 超级用户角色

角色 核心能力
root 组合了 readWriteAnyDatabase + dbAdminAnyDatabase + userAdminAnyDatabase + clusterAdmin + backup + restore 等所有关键角色的权限

root 可执行任何操作------创建/删除数据库、管理用户、集群配置、备份恢复等。严禁在日常操作中使用

7. 内置角色速查表

类别 角色名称 数据库范围 权限级别
数据库用户 read 单库 ★★☆
数据库用户 readWrite 单库 ★★★
数据库管理 dbAdmin 单库 ★★★☆
数据库管理 userAdmin 单库 ★★★★
数据库管理 dbOwner 单库 ★★★★
跨库 readAnyDatabase 全局 ★★★☆
跨库 readWriteAnyDatabase 全局 ★★★★
跨库 userAdminAnyDatabase 全局 ★★★★★
集群 clusterAdmin 全局 ★★★★★
备份恢复 backup / restore 全局 ★★★★
超级用户 root 全局 ★★★★★

三、自定义角色与集合级访问控制

内置角色虽覆盖了常见场景,但生产环境中的权限需求往往更加精细。当内置角色无法满足需求时,必须创建自定义角色。

1. 何时需要自定义角色?

  • 集合级权限隔离 :同一数据库下不同集合需要独立的读写权限时。例如,订单库中 order_main 仅读、order_log 可写。
  • 操作级精细化 :只允许 findinsert,但不允许 updatedelete
  • 业务语义控制 :内置角色只管理"能不能执行 find",不管"能不能删 user:123 的订单"------这类业务权限必须在应用层建模。
  • 多业务线隔离:每个业务线专属数据库配最小权限角色,禁用粗粒度的内置角色。

2. 创建自定义角色的完整语法

使用 db.createRole() 方法创建自定义角色:

javascript 复制代码
db.createRole({
  role: "<角色名称>",
  privileges: [
    {
      resource: { <资源定义> },
      actions: [ "<操作1>", "<操作2>", ... ]
    }
  ],
  roles: [
    { role: "<继承角色>", db: "<数据库>" }
  ],
  authenticationRestrictions: [
    {
      clientSource: ["<IP地址>", "<CIDR范围>"],
      serverAddress: ["<IP地址>", "<CIDR范围>"]
    }
  ]
})

参数说明

字段 类型 说明
role 字符串 新角色的名称
privileges 数组 授予角色的权限,由资源(resource)和操作(actions)组成
roles 数组 该角色继承权限的源角色列表
authenticationRestrictions 数组 可选,IP 地址或 CIDR 范围限制

前置条件 :创建角色需要拥有 userAdminuserAdminAnyDatabase 角色。

3. 集合级访问控制实战:电商订单系统权限设计

以电商系统为例,我们为订单数据库设计一套精细的权限矩阵:

业务需求

  • 订单主表 orders.order_main:客服团队只读
  • 订单日志表 orders.order_log:审计团队可读写
  • 退款表 orders.refund:财务团队可读写,其他团队不可见
  • 不允许任何非管理员账号删除数据

创建自定义角色

javascript 复制代码
// 切换到 orders 数据库
use orders

// 1. 客服只读角色 - 仅限 order_main 集合
db.createRole({
  role: "customer_service_read",
  privileges: [
    {
      resource: { db: "orders", collection: "order_main" },
      actions: [ "find", "listCollections" ]
    }
  ],
  roles: []
})

// 2. 审计读写角色 - 仅限 order_log 集合
db.createRole({
  role: "audit_read_write",
  privileges: [
    {
      resource: { db: "orders", collection: "order_log" },
      actions: [ "find", "insert", "update" ]
    }
  ],
  roles: []
})

// 3. 财务角色 - refund 集合完全控制,但无删除权限
db.createRole({
  role: "finance_operator",
  privileges: [
    {
      resource: { db: "orders", collection: "refund" },
      actions: [ "find", "insert", "update", "listCollections" ]
    }
    // 注意:没有包含 "delete" 或 "dropCollection"
  ],
  roles: []
})

// 4. 订单管理员 - 继承 readWrite,但额外限制不能删除
// 注意:由于 readWrite 包含删除权限,此处无法通过继承"取消"权限
// 正确的做法是显式列出所需操作,而非继承粗粒度角色
db.createRole({
  role: "order_manager",
  privileges: [
    {
      resource: { db: "orders", collection: "order_main" },
      actions: [ "find", "insert", "update", "listCollections" ]
    },
    {
      resource: { db: "orders", collection: "order_log" },
      actions: [ "find", "insert", "update" ]
    }
  ],
  roles: []
})

分配用户

javascript 复制代码
use orders
db.createUser({
  user: "cs_zhang",
  pwd: "<安全密码>",
  roles: [
    { role: "customer_service_read", db: "orders" }
  ]
})

4. 自定义角色的作用域与继承规则

理解自定义角色的作用域是权限设计的核心:

角色创建位置 可授予的权限范围 可继承的角色范围
非 admin 数据库 仅限该数据库的资源 仅限同一数据库中的其他角色
admin 数据库 admin 数据库、其他任何数据库、集群资源 任何数据库中的任何角色

核心要点

  • MongoDB 使用"数据库名 + 角色名"的组合来唯一定义角色
  • 所有角色信息统一存储在 admin.system.roles 集合中
  • 不要直接操作 system.roles 集合,应使用角色管理命令

5. 自定义角色创建流程

四、最小权限原则的生产实践

最小权限原则(Principle of Least Privilege)要求:仅授予用户完成任务所需的最小权限,不多一分。在 MongoDB 生产环境中,这是保障数据安全的第一道防线。

1. 为什么最小权限在 MongoDB 中尤为重要?

  • 数据泄露的放大器:一个过度授权的应用账号被攻破,攻击者即可获得远超预期的数据访问能力
  • 运维事故的催化剂 :拥有 dropDatabase 权限的账号,一次误操作即可造成灾难性后果
  • 合规审计的硬要求:等保、GDPR、SOC2 等标准均明确要求实施最小权限

2. 过度授权 vs. 最小权限------风险对照

场景 过度授权的做法 风险 最小权限的正确做法
报表应用 授予 readWriteAnyDatabase 可读写所有数据库,一旦被攻击全库沦陷 授予 read 仅限目标数据库
运维脚本 使用 root 角色 误删数据库无法挽回 创建仅包含所需操作的自定义角色
开发环境 所有开发人员共享 dbOwner 权限边界模糊,难以追责 每人独立账号,按需授权
备份账号 授予 root 执行备份 备份介质泄露即全库暴露 授予 backup 角色

3. 生产环境角色设计方法论

分层角色设计

实践要点

  1. 仅在 admin 库创建高权限账户,应用账户严格限定在目标业务库
  2. 为每个应用/人员创建唯一的 MongoDB 用户
  3. 创建角色而非直接分配权限,实现权限的复用与统一管理
  4. 避免在应用中使用 root 角色进行日常操作
  5. 定期审查权限 :使用 db.getUsers()db.getRoles({showPrivileges: true}) 进行审计

4. 生产环境用户创建的标准操作流程

javascript 复制代码
// 1. 首先创建管理员用户(第一个用户必须具有 userAdmin 权限)
use admin
db.createUser({
  user: "admin",
  pwd: "<强密码>",
  roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
})

// 2. 使用管理员账号认证后,创建应用专用用户
use orders
db.createUser({
  user: "order_app",
  pwd: "<强密码>",
  roles: [
    { role: "readWrite", db: "orders" }  // 仅限 orders 库
    // 不要授予其他数据库的任何权限
  ]
})

// 3. 创建只读报表用户
use orders
db.createUser({
  user: "report_reader",
  pwd: "<强密码>",
  roles: [
    { role: "read", db: "orders" }
  ]
})

// 4. 为 DBA 创建独立的精细化管理账号
use admin
db.createUser({
  user: "dba_zhang",
  pwd: "<强密码>",
  roles: [
    { role: "dbAdminAnyDatabase", db: "admin" },
    { role: "clusterMonitor", db: "admin" }
    // 注意:没有 userAdmin,避免权限过度集中
  ]
})

5. 分片集群的权限管理特殊注意事项

根据用户对分片集群的架构选择(选项1C:副本集为主,补充分片集群注意事项),以下补充分片集群的关键差异:

分片集群的用户创建位置

  • 常规操作为:连接到 mongos 实例,在 mongos 上创建用户
  • 用户信息存储在 config 服务器的 admin 数据库
  • 如需创建仅针对特定分片的本地用户,需直接登录该分片的 primary 节点

分片集群的组件间认证

  • 分片集群各组件(mongosmongodconfig 服务器)之间需要使用 KeyFile 进行内部身份认证
  • 在生产环境中,正确的顺序是:先在本地创建 root@admin → 开启 authorization → 按库分权

分片集群的特殊角色

  • directShardOperations 角色:允许自动化代理执行需要直接访问分片的维护操作(如添加分片),仅分片集群需要

五、审计与合规------满足等保要求的实践指南

1. MongoDB 审计日志概述

MongoDB 的审计日志可完整记录所有管理操作及数据变更。等保三级要求保留 6 个月以上的审计日志,且日志需满足"不可篡改"要求。

审计日志包含的关键事件类型:

  • 认证事件:登录成功/失败
  • 授权操作:角色变更、权限授予/撤销
  • 数据库操作:CRUD 操作

2. 审计日志配置

yaml 复制代码
# mongod.conf
auditLog:
  destination: file
  format: JSON
  path: /var/log/mongodb/audit.log
  filter: '{ "atype": { "$in": ["authCheck", "createUser", "dropUser", "grantRoles", "revokeRoles"] } }'

配置要点

  • 通过 auditLog.destination 指定输出目标(文件/syslog/控制台)
  • 设置 auditLog.formatJSON 以便解析和归档
  • 使用 filter 过滤审计事件,避免日志过度膨胀

3. 权限审计的常用命令

javascript 复制代码
// 查看所有用户及其角色
db.getUsers()

// 查看角色的详细权限(含继承展开)
db.getRoles({ rolesInfo: 1, showPrivileges: true, showBuiltinRoles: true })

// 查看特定用户的权限详情
db.getUser("order_app", { showPrivileges: true })

// 审计日志分析(示例:查询最近的角色变更操作)
// 需配合日志分析工具(如 ELK)对 JSON 格式的审计日志进行检索

实践建议

  • 建立每周权限审查机制,导出所有用户权限清单
  • 对高权限角色(rootuserAdminAnyDatabaseclusterAdmin)的使用进行月度专项审计
  • 所有权限变更必须关联工单号,确保可追溯

六、常见面试题

Q1:MongoDB 默认是否启用访问控制?如何启用?

参考答案

MongoDB 默认不启用访问控制。需要在启动时通过以下方式之一启用:

  1. 命令行参数:mongod --auth
  2. 配置文件:在 mongod.conf 中设置 security.authorization: enabled

启用后,所有客户端连接都必须通过身份验证。第一个用户必须在启用前创建(或通过 localhost 豁免机制在启用后创建)。

Q2:readreadWrite 角色的区别是什么?dbOwner 包含了哪些权限?

参考答案

  • read:仅提供读取 权限(findlistCollections 等),适用于只读报表账号
  • readWrite:提供 read 的全部权限 + 写入 权限(insertupdatedelete

dbOwner 是数据库所有者的角色,组合了 readWrite + dbAdmin + userAdmin 三个角色的全部权限。它是一个高权限角色,应谨慎使用。

Q3:如何创建一个只能对 products.inventory 集合执行 findinsert 操作的自定义角色?

参考答案

javascript 复制代码
use products
db.createRole({
  role: "inventory_operator",
  privileges: [
    {
      resource: { db: "products", collection: "inventory" },
      actions: [ "find", "insert" ]
    }
  ],
  roles: []  // 不继承任何角色,确保权限最小化
})

关键点:

  • 使用 resource 精确定位到 products.inventory 集合
  • actions 数组只包含 findinsert,不包含 updatedelete
  • roles 为空数组,避免继承不必要的权限

Q4:一个用户同时拥有 read(orders 库)和 readWriteAnyDatabase 两个角色,他对 orders 库的权限是什么?

参考答案

该用户对 orders 数据库拥有读写权限

原因:MongoDB 的角色从不限制权限 。当用户拥有多个角色时,具有更高访问权限的角色优先。readWriteAnyDatabase 包含对所有数据库的读写权限,因此 read 角色不会撤销对 orders 库的写入能力。

重要推论 :不能通过授予一个低权限角色来"抵消"另一个高权限角色。撤销权限必须使用 revokeRolesFromUser

Q5:在非 admin 数据库中创建的角色,能否继承 admin 数据库中角色的权限?

参考答案

不能

规则如下:

  • 非 admin 数据库 中创建的角色:只能继承同一数据库中的其他角色
  • admin 数据库 中创建的角色:可以继承任何数据库中的角色

因此,如果需要在非 admin 库的角色中继承 admin 库的权限,必须将该角色创建在 admin 数据库中。

Q6:生产环境中,应用账号应该使用什么角色?为什么不能用 root

参考答案

生产环境中的应用账号应使用最小必要权限的角色:

  • 如果只需读取,使用 read 限定到目标数据库
  • 如果需要读写,使用 readWrite 限定到目标数据库
  • 如需更精细的控制,使用自定义角色精确到集合级别

禁止使用 root 的原因

  1. root 拥有全库所有操作的权限,一旦应用被攻破,整个数据库集群沦陷
  2. 运维事故风险极高:一次误操作(如 db.dropDatabase())即可造成灾难
  3. 不符合最小权限原则,无法通过合规审计

Q7:分片集群中,用户应该在哪里创建?用户信息存储在哪里?

参考答案

  • 常规情况下,应连接到 mongos 实例创建用户
  • 用户信息存储在 config 服务器的 admin 数据库
  • 如果需要在特定分片 上创建本地用户(例如分片级别的维护账号),需直接登录该分片的 primary 节点,此时用户信息存储在该分片的 admin 数据库中

分片集群的组件间还需使用 KeyFile 进行内部身份认证。