为每个租户数据库配专属账号是mongodb多租户隔离强度最高、最防数据越界的方案,依赖原生权限系统而非应用层过滤,需严格确保createuser的db参数与租户库名一致、角色限于数据库级、连接串database匹配、迁移脚本模板化且密码动态生成。

直接给每个租户数据库配一个专属账号,是 MongoDB 多租户中隔离强度最高、最不容易出数据越界事故的做法。它不依赖应用层过滤 tenant_id,而是靠 MongoDB 原生的权限系统在服务端拦住非法访问。
createUser 命令必须指定 db 参数,且值为租户专属数据库名
这是最关键的一步。MongoDB 的用户权限是「数据库粒度」绑定的,createUser 时如果漏写 db 或写错,账号就可能被创建在 admin 或其他共享库下,导致权限失控。
-
db参数必须和租户实际使用的数据库名完全一致,比如租户 ID 是acme_001,数据库名就得是acme_001(或按规范拼接如tenant_acme_001) - 不能在
admin库里执行createUser却把roles指向别的库——这种跨库授权虽支持,但会放大误操作风险 - 示例正确写法(在
acme_001库上下文中执行):db.runCommand({ createUser: "acme_user", pwd: "xxx", roles: [{ role: "readWrite", db: "acme_001" }] })
角色必须用数据库级角色,禁用 clusterAdmin 等集群级权限
租户账号只应拥有其数据库内的最小必要权限。一旦授予 clusterAdmin、root 或 dbAdminAnyDatabase 这类高危角色,等于把整个实例的控制权交出去。
- 首选
readWrite、read、dbOwner(仅当租户需自主建索引/集合时谨慎启用) - 绝对避免
__system角色或任何带AnyDatabase后缀的角色 - 如果租户需要运行聚合或 mapReduce,确认其角色已包含
readWrite(它隐含find和collStats),无需额外加clusterMonitor
连接字符串必须显式指定 database,且与账号所属库一致
应用代码里用的 mongodb:// 连接串,database 参数(即 URL 路径部分)必须和账号创建时绑定的 db 完全匹配。否则即使账号权限正确,MongoDB 也会因认证上下文错位而拒绝操作。
- 错误写法:
mongodb://acme_user:xxx@host:27017/admin(账号属于acme_001库,却连admin) - 正确写法:
mongodb://acme_user:xxx@host:27017/acme_001 - Spring Boot 配置中对应字段是
spring.data.mongodb.database=acme_001,不是default_database这类模糊配置
golang-migrate/migrate 的 JSON 脚本要按租户动态生成
用 golang-migrate/migrate 管理账号创建时,不能写死一个脚本复用所有租户。因为每个租户的数据库名、用户名、密码都不同,硬编码会导致迁移失败或覆盖已有账号。
- 迁移脚本应是模板化 JSON,由部署流程注入变量,例如:
[ { "createUser": "${TENANT_USER}", "pwd": "${TENANT_PWD}", "roles": [ { "role": "readWrite", "db": "${TENANT_DB}" } ] } ] - 确保密码生成符合强度要求(至少 12 位,含大小写字母+数字+符号),且每次迁移都生成新密码,不复用
- 脚本执行前,先用
db.getUser("xxx")检查用户是否存在,避免重复创建报错中断流程
真正难的不是创建账号,而是确保整个生命周期里——从 CI/CD 自动化部署、到租户自助开通、再到账号轮换或删除——所有环节都严格遵循「一库一账一连」原则。任何绕过这套规则的手动操作(比如 DBA 直接在 admin 库里加用户),都会瞬间瓦解隔离防线。











