真正实现租户访问隔离必须采用“每个租户一个数据库+专属账号”方案,因分片键和Zone Sharding仅控制数据分布与物理调度,不提供任何访问控制;RBAC权限校验依赖数据库上下文而非分片归属,漏配authSource或跨库操作将导致越权。

在 MongoDB 分片集群中,shardCollection 本身不提供租户访问隔离能力,真正的权限控制必须靠「租户专属数据库 + 专属账号」组合实现;分片只是物理分布手段,不是安全边界。
为什么不能靠分片键或 Zone Sharding 控制租户访问
分片键(如 {tenantId: 1})只影响数据路由和 chunk 分布,不拦截非法查询。即使所有 acme_001 数据都落在 shard02,一个带错 tenantId 的查询仍会由 mongos 广播到所有分片——RBAC 不校验分片归属,只看账号权限上下文。
sh.addShardToZone("shard01", "tenant-a") 这类操作本质是物理调度指令,对客户端连接、认证、查询无任何过滤作用。它不解析协议、不检查 tenantId 字段、也不拒绝跨租户请求。
- 常见错误:以为加了 zone 就等于“租户独占分片”,结果运维发现
db.orders.find({})依然扫全集群 - 配置链脆弱:
sh.enableSharding()→sh.shardCollection()→sh.addShardToZone()→sh.updateZoneKeyRange(),漏一步就失效 - 新增租户需手动绑 range、调 chunk,无法自动化,不适合动态扩租场景
必须用每个租户一个数据库 + 专属账号
这是唯一能让 MongoDB 原生 RBAC 在服务端硬拦截越权的路径。分片集群中,mongos 会把权限校验透传给目标 mongod,只要账号在对应库创建、角色绑定正确、连接串匹配,越界操作直接返回 not authorized,而非静默返回错误数据。
-
createUser必须在租户库上下文中执行:先use acme_001,再运行db.runCommand({ createUser: "...", roles: [{ role: "readWrite", db: "acme_001" }] }) - 连接串必须写成
mongodb://user:pass@mongos:27017/acme_001?authSource=acme_001——authSource和 URL 路径必须一致,否则认证通过但后续操作全被拒 - 禁止使用
dbAdminAnyDatabase或root角色;租户账号只能授readWrite或必要时dbOwner - Spring Boot 中必须用
spring.data.mongodb.uri配完整 URI,不要拆成database+authenticationDatabase两个字段
GridFS 和事务中的租户隔离陷阱
GridFS 默认用 fs 桶,所有租户共用 fs.files 和 fs.chunks,仅靠 metadata.tenantId 过滤极易越界。事务中漏写 tenantId 条件,原子性反而会固化跨租户污染。
- GridFS 必须为每个租户设独立
bucketName,如tenant_acme_001,并为对应tenant_acme_001.files和tenant_acme_001.chunks单独授读写权限 - 上传/下载时必须动态构造
GridFSBucket实例,不能复用全局单例;bucketName必须从 JWT 或请求上下文实时提取 - 事务内所有操作(包括
$lookup后的$match)都得显式带{ tenant_id: "acme_001" };索引必须以tenant_id为前导字段,否则过滤失效且可能内存超限 - 跨数据库事务不支持:
session.startTransaction()无法同时操作acme_001.users和acme_002.orders,多租户一致性只能靠应用层补偿
最易被忽略的一点:分片集群中 config 库的元数据(如 config.databases、config.collections)是全局可见的,listDatabases 权限虽不泄露数据,但会暴露全部租户库名——若业务敏感,必须限制该权限,或改用 Atlas 等托管服务的项目级隔离。


















