租户隔离唯一可靠方式是“一租户一库一账号”,需严格匹配数据库名与authsource、动态构造gridfsbucket并校验tenantid白名单,否则rbac无法感知租户上下文易导致越界。

每个租户必须用独立数据库 + 独立账号
生产环境唯一可靠的隔离方式是「一个租户一个数据库 + 一个数据库一个专属账号」。其他方案——比如共享数据库、靠tenant_id字段过滤、或复用全局用户——都依赖应用层兜底,漏写条件就直接越界。
常见错误现象:createUser在admin库执行,却把roles.db设为租户库名;结果账号存在admin下,权限绑定错位,运维难审计、权限易被绕过。
- 必须先
use acme_001,再运行db.runCommand({ createUser: "acme_user", pwd: "xxx", roles: [{ role: "readWrite", db: "acme_001" }] }) -
db参数值必须和租户数据库名完全一致(如租户 ID 是acme_001,就不能写成tenant_acme_001,除非连接串也同步匹配) - Spring Boot 中禁用
spring.data.mongodb.database+spring.data.mongodb.authentication-database拆分配置,必须用spring.data.mongodb.uri传完整 URI
连接串必须同时指定 database 和 authSource 且二者一致
只写mongodb://u:p@host:27017/是危险的默认行为:驱动会以admin为authSource,但操作上下文不绑定具体库,极易因上下文错位导致认证通过却操作被拒。
正确连接串格式:mongodb://acme_user:xxx@host:27017/acme_001?authSource=acme_001
-
database(URL 路径部分)决定默认操作库 -
authSource决定密码和角色存储位置 - 两者不一致时,即使账号存在、密码正确,
insert或find也会返回not authorized on acme_001 to execute command
GridFS 必须按租户隔离 bucketName 并授集合级权限
共用默认fs桶等于裸奔:所有租户的fs.files和fs.chunks混存,仅靠metadata.tenantId过滤,直连数据库或聚合漏条件就会跨租户读取。
必须为每个租户动态构造GridFSBucket实例,bucketName设为tenant_${tenantId}形式(如tenant_acme_001),且权限只给对应两个集合:
- 授予
tenant_acme_001.files和tenant_acme_001.chunks的readWrite权限 - 禁止使用
readWrite数据库角色——它允许访问该库下所有集合 -
tenantId必须白名单校验(只允许字母、数字、下划线、短横线,且不能以数字开头) - 不能复用全局单例;每次请求都应基于当前上下文实时生成
GridFSBucket
副本集与分片集群中 RBAC 不自动校验租户上下文
副本集共享oplog和端口,分片集群中mongos可能广播无tenant_id条件的查询——这意味着即使你做了上述所有隔离,仍可能被底层机制绕过。
最容易被忽略的一点:RBAC 权限检查发生在 mongod/mongos 层,但它不感知“租户”这个业务概念。它只认db和collection,不校验文档里有没有tenant_id或值是否匹配当前会话。
所以,tenant_id字段漏写、拼写错误、类型不一致(string vs ObjectId)、索引没建对前导字段——这些都会让隔离形同虚设,而错误日志里往往只报not authorized或静默返回空结果,很难定位。











