分片键设为tenant_id不能替代权限隔离,因分片仅负责数据分布而非访问控制;必须结合租户专属数据库与数据库级权限(如readwrite角色绑定到具体租户库)实现硬隔离。

MongoDB 6.0 分片集群中,**仅靠分片本身不提供租户隔离能力**;真正的数据隔离必须由「数据库级权限 + 租户专属数据库」组合实现,分片键选 tenant_id 仅影响数据分布,不防越权访问。
为什么分片键设为 tenant_id 不能替代权限隔离
分片是物理分布机制,不是访问控制机制。即使你用 sh.shardCollection("db.orders", { tenant_id: "hashed" }) 把同一租户数据打散到多个分片上,只要账号有跨库权限或连接串错配,照样能扫出其他租户数据。
常见错误现象:
- 误以为“数据分散了就安全了”,结果租户账号被授予
readWriteAnyDatabase,直接查遍所有tenant_*库 - 在
admin库执行createUser,却把roles.db设为"tenant_acme"—— 这属于跨库授权,mongos不校验租户上下文,认证通过但操作可能被拒或越界 - 连接串写成
mongodb://u:p@mongos:27017/?authSource=admin,导致认证走admin,但默认操作库是test,上下文完全错位
createUser 必须在目标租户库中执行且 db 参数严格匹配
MongoDB 的用户角色绑定到具体数据库,createUser 命令的 db 字段值必须和租户实际使用的数据库名完全一致,且命令必须在该库上下文中运行。
正确做法:
- 先
use tenant_acme_001 - 再执行
db.runCommand({ createUser: "acme_user", pwd: "xxx", roles: [{ role: "readWrite", db: "tenant_acme_001" }] })
错误写法(高危):
- 在
admin库执行同上命令 → 用户创建在admin,权限失控 -
roles.db: "tenant_acme_001"但连接串路径是/tenant_acme_002→ 认证成功,后续所有操作被拒 - 数据库名含非法字符(如大写字母、点号),导致
bucketName或集合名不合法,GridFS 初始化失败
连接串必须同时指定 database 和 authSource 且二者一致
在分片集群中,mongos 不转发 authSource 到分片节点,所以 authSource 必须是租户自己的数据库(而非 admin),否则认证虽过,但分片节点查不到用户凭证。
正确连接串示例:
mongodb://acme_user:xxx@mongos1:27017,mongos2:27017/tenant_acme_001?authSource=tenant_acme_001&replicaSet=rs0
关键点:
-
/tenant_acme_001是 URL 路径部分,决定默认操作库 -
authSource=tenant_acme_001决定密码和角色存储位置 - 二者不一致时,
mongos可能从authSource加载用户,却在别的库执行命令,触发not authorized on xxx to execute command
GridFS 和聚合管道中 tenant_id 容易漏掉的硬隔离点
即使用了独立数据库,GridFS 和聚合仍可能因配置疏忽导致越界。
GridFS 必须动态构造实例并设独立 bucketName:
- 不能复用全局
GridFSBucket实例;每次请求需根据tenantId动态生成:new GridFSBucket(db, { bucketName: `tenant_${tenantId}` }) - 必须对
tenantId做白名单校验(只允许[a-z0-9_]),防止注入非法集合名 - 权限必须落到
tenant_acme_001.files和tenant_acme_001.chunks两个集合,禁用readWrite数据库角色
聚合管道中 $lookup 易漏过滤:
-
$lookup目标集合是硬编码的(如from: "orders"),必须在后续$match阶段显式加{ tenant_id: "$$tenantId" } - 不能依赖中间件自动注入;直连
mongosh或 CLI 工具时,$$tenantId变量根本不存在 - 事务内执行聚合,漏写
tenant_id条件会导致跨租户污染,且 MongoDB 不报错
最常被忽略的是:分片集群中 mongos 会广播无 tenant_id 条件的查询(比如 countDocuments({})),一旦租户账号权限过大,这条语句就能扫出全量数据——而它看起来毫无异常。











