MongoDB的细粒度权限控制必须在分片节点(mongod)上实现,因为mongos仅负责路由和聚合,不执行集合或字段级权限校验;真实权限检查由目标分片上的mongod完成,mongos仅支持通过OIDC角色映射间接控制访问范围。

在 Mongos 层级无法实现真正意义上的“细粒度权限控制”,因为 mongos 不存储数据、不执行集合级权限校验,它只做路由和聚合。所有基于集合(collection)或字段(field)的权限策略,必须在分片节点(mongod)上生效 —— 但你可以在 mongos 上配置角色映射,让其把 OIDC/LDAP 声明中的 group 或 scope 映射为内置/自定义角色,从而间接达成按身份来源控制访问范围的目的。
为什么 mongos 本身不校验 collection 级权限
mongos 的权限系统仅用于认证和路由前的初步拦截,它不解析查询语句、不检查目标集合是否存在、也不校验用户对某个 db.collection 是否有 find 权限。真实权限检查由目标分片上的 mongod 实例完成。这意味着:
- 你在
mongos上执行db.runCommand({ rolesInfo: "readWrite" })返回的是mongos自己维护的角色定义,和分片无关 - 即使
mongos中创建了{ role: "customRole", db: "myapp" },该角色是否生效,取决于每个分片是否也定义了同名角色且资源匹配 - 如果某个分片没同步该角色定义,用户连到该分片时会直接报
not authorized on myapp to execute command { find: ... }
OIDC 场景下用 mongos 做 role mapping 的实操要点
这是目前唯一被官方支持、且能在 mongos 层级“集中管理权限映射”的方式。前提是启用 OIDC,并在 mongos 配置中开启 security.oidc.roleMapping。
- 确保
mongos启动时带--config /path/to/mongos.conf,其中包含:security: oidc: servers: - issuer: https://idp.example.com clientId: mongo-client clientSecret: <secret> roleMapping: - claim: groups values: ["dev-team"] role: readWrite database: myapp - claim: scopes values: ["admin:all"] role: root database: admin</secret> - 用户登录时携带的 JWT 必须包含
groups或scopes字段,值需严格匹配values列表中的字符串(区分大小写) - 映射出的角色必须已在对应数据库中存在 —— 比如
myapp库里得提前运行过db.createRole(...),否则映射失败且静默忽略 - 该机制不替代传统用户密码认证,而是叠加在 OIDC 认证流程之后;普通
db.createUser()用户不受此映射影响
想控制某用户只能查 orders 表里的 status 字段?别在 mongos 上折腾
字段级权限(Field Level Security)MongoDB 原生不支持。所谓“细粒度”,实际能落地的只有三类:
-
数据库级:用
read/readWrite角色限定库范围,最常用也最稳 -
集合级:通过自定义角色 +
privileges.resource.collection指定具体集合,但必须在每个分片上重复创建该角色 -
命令级:比如禁用
dropDatabase,靠deny特权(Enterprise 版专属),同样需各分片一致
如果你硬要在 mongos 上建一个只针对 orders 的角色,结果就是:查询能过路由,但落到分片后因角色缺失或权限不匹配而被拒绝 —— 错误信息看起来像网络问题,实际是权限不同步。
最容易被忽略的坑:分片角色未同步就上线新用户
运维中最常翻车的不是配置写错,而是忘了跑同步脚本。假设你在 mongos 上创建了用户 app-reader 并赋予 myapp.readOnly 角色,但没在所有分片上执行:
use myapp
db.createRole({
role: "readOnly",
privileges: [{
resource: { db: "myapp", collection: "orders" },
actions: ["find"]
}],
roles: []
})
那么该用户首次查询 orders 时,mongos 会把它发给某个分片主节点,而那个节点根本不知道 readOnly 是什么 —— 直接返回 not authorized。排查时容易误判为网络或认证失败,其实只是角色定义漏了。











