生产环境必须采用“每个租户一个数据库+专属账号”方案,createUser须在目标库执行,连接串需同时指定database和authSource且二者一致,GridFS须按租户隔离bucketName并授集合级权限,副本集与分片集群中RBAC不自动校验租户上下文,漏写tenant_id将导致越界访问。

生产环境必须用「每个租户一个数据库 + 每个数据库一个专属账号」组合,其他方案都靠应用层兜底,漏写 tenant_id 就越界。
createUser 必须在目标库上下文中执行
MongoDB 的用户权限是数据库粒度绑定的,createUser 命令若不在租户库中执行,或 db 参数写错,账号就可能被创建在 admin 库下,导致权限失控。
- 正确做法:先
use acme_001,再运行db.runCommand({ createUser: "acme_user", pwd: "xxx", roles: [{ role: "readWrite", db: "acme_001" }] }) - 错误写法:在
admin库里执行createUser,却把roles.db设为"acme_001"—— 这属于跨库授权,运维难审计、误操作风险高 -
db参数值必须和租户实际数据库名完全一致,比如租户 ID 是acme_001,数据库名就得是acme_001(不能是tenant_acme_001除非连接串也同步匹配)
连接串必须同时指定 database 和 authSource
只写 mongodb://u:p@host:27017/ 是危险的默认行为:驱动会以 admin 为 authSource,但操作上下文不绑定具体库,极易因上下文错位导致权限失效。
- 正确连接串:
mongodb://acme_user:xxx@host:27017/acme_001?authSource=acme_001 -
database(URL 路径部分)决定默认操作库,authSource决定密码和角色存储位置;两者必须一致,否则认证通过但操作被拒 - Spring Boot 中必须用
spring.data.mongodb.uri配置完整 URI,不要拆成database+ 单独的authenticationDatabase字段,后者不参与authSource解析
GridFS 多租户必须用独立 bucketName + 集合级权限
GridFS 默认用 fs bucket,所有租户共用 fs.files 和 fs.chunks,仅靠元数据字段(如 metadata.tenantId)过滤,一旦查询漏条件或直连数据库,就会跨租户读取。
- 每个租户必须动态构造
GridFSBucket实例,bucketName设为tenant_${tenantId}形式(如tenant_acme_001),且需白名单校验tenantId字符合法性 - 不能复用全局单例,也不能缓存到模块顶层——
bucketName必须随请求上下文实时生成 - 权限必须落到集合级别:为该租户账号授予
tenant_acme_001.files和tenant_acme_001.chunks的读写权限,禁用readWrite数据库角色
副本集与分片集群中权限容易被绕过的点
副本集共享 oplog 和端口,分片集群中 mongos 会广播无 tenant_id 条件的查询——RBAC 不校验 IP、租户上下文或业务标识,全靠连接层和应用层双重把关。
-
readPreference可能绕过权限检查:若配置为nearest且某节点未启用访问控制,直连该节点可跳过认证 - Zone Sharding 不提供租户隔离能力,它只按分片键路由,不理解租户语义;即使把所有
tenantId: "acme"数据落到某 shard,漏写tenant_id的查询仍会广播到所有分片 - 事务内漏写
tenant_id更危险:事务保证原子性,但不保证租户边界,updateMany({ status: "paid" })在事务中执行,会稳稳删掉所有租户的已支付订单
真正容易被忽略的是:权限配置正确 ≠ 隔离生效。只要连接串错、authSource 错、bucketName 复用、或任意一次查询漏 tenant_id,隔离就瞬间崩塌。生产环境必须把校验点卡死在驱动初始化、请求入口、聚合管道每一步,而不是依赖“应该不会出错”的假设。

















