1. 项目背景
业务场景:本地生活电商上线 6 个月,运行平稳——直到某天凌晨 3 点,运维被告警电话吵醒。一个外包开发离职前把自己的 MongoDB 账号分享到了公共 GitHub 仓库,黑客用这个账号在凌晨删除了 3 个核心集合,还在一个集合里留下了勒索信息"支付 0.5 BTC 到以下地址,否则数据永久丢失"。更可气的是,安全审查发现:所有服务共用一个 root 账号,没有区分读写权限;开发环境的 MongoDB 端口 27017 直接暴露在公网上,没有任何防火墙;TLS 连接加密从未开启——数据传输都是明文。
痛点:MongoDB 默认安装下没有任何安全机制,不配置等于"裸奔"。常见安全隐患:使用 root 账号做所有操作——一个应用挂掉误写了 admin 库,整个数据库集群权限被破坏;所有团队共用密码、明码写在配置文件中;不区分开发/测试/监控账号,监控系统用的也是 root;数据库端口没有 IP 白名单,任何人都能尝试连接;从 2017 年起有多起大规模的 MongoDB 勒索攻击,就是利用未认证的 MongoDB 实例直接删除数据并勒索。
2. 项目设计:剧本式交锋对话
小胖(脸色苍白地看新闻):大师!我刚看到一篇报道——一家公司 MongoDB 没设密码,被黑客删库勒索了 0.5 个比特币!我们的 MongoDB 是不是也很危险?
大师:你们目前用的是什么账号?
小胖:呃……admin / admin123。
大师(叹气):那就是裸奔。MongoDB 安全有三个基本防线——认证(你是谁)、授权(你能做什么)、加密(你的数据在路上安不安全)。目前你们三道防线全没守住。
小胖:认证和授权有区别吗?登录不就是确定权限吗?
大师:是两步。“认证"是 MongoDB 回答"你是谁”,通过用户名密码或证书完成;“授权"是回答"你能做什么”,通过角色(Role)来管理。认证成功后,你的操作被分配给角色的权限框架校验。
技术映射:MongoDB 的认证默认使用 SCRAM(Salted Challenge Response Authentication Mechanism),密码不会以明文形式在网络上传送。授权基于 RBAC(Role-Based Access Control),每个用户被授予一个或多个角色,角色定义了哪些操作可以在哪些数据库/集合上执行。
小白:我看到 MongoDB 有很多内置角色——read、readWrite、dbAdmin、clusterMonitor、root……这些怎么区分?
大师:画张表最清楚:
| 角色 | 权限范围 | 适用对象 |
|---|---|---|
read | 仅读取指定数据库 | 只读报表、数据分析师 |
readWrite | 读写指定数据库 | 业务应用服务 |
dbAdmin | 管理数据库(索引、统计、清理) | DBA(不含读写数据) |
userAdmin | 管理用户和角色 | 安全管理员 |
clusterAdmin | 管理集群(复制集、分片) | 运维/DBA |
clusterMonitor | 查看集群状态(无写权限) | Prometheus 监控 |
backup/restore | 备份和恢复 | 备份系统专用账号 |
root | 所有权限 | 仅超级管理员,禁止日常使用 |
小胖:那最小权限原则就是——开发用 readWrite,监控用 clusterMonitor,备份用 backup,日常操作绝不给 root?
大师:一针见血。还有一个技巧:账号分层。每一层(开发环境、测试环境、预发布、生产环境)用不同的用户名和密码,不要复用。开发环境的密码泄露不会影响生产环境。
技术映射:db.createUser()创建用户,db.grantRolesToUser()追加角色,db.revokeRolesFromUser()撤销角色。第 3 章讲过的authenticationDatabase就是指用户被定义在哪个库——root 和系统级用户在 admin 库,业务用户在各自业务库。
小白:那 TLS/SSL 连接加密呢?是不是配个证书就行了?
大师:原理跟你 HTTPS 访问网站一样——客户端和服务端各自持有证书,建立加密通道。MongoDB 的 TLS 可以双向认证(客户端也需证书),但多数项目只做单向(服务端出示证书)。配置 TLS 的三个要点:
- 需要一个有效的 CA 签发的证书(内网可用自签 CA)。
- 服务端证书的 CN 或 SAN 必须与连接串中的主机名匹配。
- 启用 TLS 后,端口不变(27017),但传输内容加密。
小胖:还有个问题——MongoDB 偶尔曝出严重漏洞(比如 CVE),我们怎么知道什么时候该打补丁?
大师:这属于安全运维的持续过程。建议订阅 MongoDB 的安全公告邮件;生产环境用 MongoDB 的稳定版本(偶数版本号如 7.0、8.0),避开刚出的 .0 版本;Docker 部署的好处是可以快速滚动升级镜像版本而不停服务(配合复制集)。
大师(总结):今天的三件事——建账号分层体系,给每个服务/角色独立的用户名和最小权限;如果只是在内网跑,至少也要开 SCRAM 认证;如果数据是用户的 PII(个人信息),TLS 加密是底线。明天开始,先把 admin123 改掉。
3. 项目实战
3.1 环境准备
沿用第 2 章的 Docker MongoDB,它已在docker-compose.yml中配了 root 用户。
3.2 分步实现
步骤一:创建分层账号体系
目标:为开发、测试、只读报表、监控系统创建独立账号,遵循最小权限原则。
// 使用 root 账号连接use admin// 1. 创建业务应用账号(local_life 库的读写权限)db.createUser({user:"app_life",pwd:"App_Life_P@ss_2026",roles:[{role:"readWrite",db:"local_life"}// 仅在 local_life 库有读写]})// 2. 创建只读报表账号db.createUser({user:"report_reader",pwd:"Report_Read_Only_2026",roles:[{role:"read",db:"local_life"}// 仅可读取]})// 3. 创建测试账号db.createUser({user:"tester_life",pwd:"Test_Life_2026",roles:[{role:"readWrite",db:"local_life_test"}]})// 4. 创建监控专用账号(不读写数据,只看集群状态)db.createUser({user:"monitor",pwd:"Monitor_2026",roles:[{role:"clusterMonitor",db:"admin"},// 查看集群指标{role:"readAnyDatabase",db:"admin"}// 可读所有库的元数据(可选)]})// 5. 创建备份专用账号db.createUser({user:"backup_user",pwd:"Backup_2026_Secure",roles:[{role:"backup",db:"admin"},{role:"restore",db:"admin"}]})// 查看所有用户db.getUsers()步骤二:验证权限边界
目标:用不同账号连接并尝试越权操作,验证权限正确生效。
// == 场景1:报表账号尝试写入 ==// 用 mongosh 连接 report_reader// mongosh "mongodb://report_reader:Report_Read_Only_2026@localhost:27017/local_life?authSource=admin"// 尝试读取(应成功)use local_life db.products_idx.findOne()// → 应该成功返回// 尝试写入(应失败)db.products_idx.insertOne({name:"越权测试"})// → 错误: not authorized on local_life to execute command { insert: "products_idx" ... }// == 场景2:测试账号尝试读写 local_life 库 ==// 连接 tester_life// mongosh "mongodb://tester_life:Test_Life_2026@localhost:27017/local_life?authSource=admin"use local_life db.products_idx.findOne()// → 错误: not authorized on local_life(该账号只有 local_life_test 的权限)// == 场景3:监控账号尝试读写数据 ==// mongosh "mongodb://monitor:Monitor_2026@localhost:27017/admin?authSource=admin"use local_life db.serverStatus()// 查看服务器状态(应成功,因为 clusterMonitor 角色有权限)db.products_idx.findOne()// → 错误: not authorized(clusterMonitor 没有数据读写权限)步骤三:密码安全——修改密码与密钥轮换
目标:学习修改密码、吊销账号、启用密码检查。
// 使用 root 账号连接use admin// 修改密码db.changeUserPassword("app_life","App_Life_N3w_P@ss_2026Q2")// 验证新密码生效// 用旧密码连接 → 应失败// 用新密码连接 → 应成功// 吊销账号权限(保留用户但撤销角色)db.revokeRolesFromUser("tester_life",[{role:"readWrite",db:"local_life_test"}])// 完全删除用户db.dropUser("tester_life")// 启用密码复杂度检查(MongoDB 企业版)// MongoDB 社区版不内置密码策略,需在应用层做或使用外部认证// Atlas 云服务默认有密码强度校验步骤四:为应用账号授予自定义角色
目标:创建自定义角色——允许在商品集合上执行特定操作。
use local_life// 创建自定义角色——商品管理员db.createRole({role:"productManager",privileges:[{resource:{db:"local_life",collection:"products"},actions:["find","insert","update","remove"]},{resource:{db:"local_life",collection:"products"},actions:["createIndex","dropIndex"]}],roles:[]// 不继承其他角色})// 将自定义角色授予用户db.createUser({user:"product_admin",pwd:"Product_Admin_2026",roles:[{role:"productManager",db:"local_life"}]})// 验证:product_admin 只能操作 products 集合// 连接 product_admin// mongosh "mongodb://product_admin:Product_Admin_2026@localhost:27017/local_life?authSource=local_life"use local_life db.products.insertOne({name:"权测试"})// → 应成功(products 集合)db.users.findOne()// → 应失败(无 users 集合权限)步骤五:连接加密——自签 TLS 证书配置(概述)
目标:了解 MongoDB TLS 加密的配置流程(生产环境需正规 CA 证书)。
# TLS 配置流程(以自签证书为例,仅在本地测试环境使用)# 1. 生成 CA 私钥和证书openssl genrsa-outca.key2048openssl req-x509-new-nodes-keyca.key-sha256-days365-outca.crt\-subj"/CN=MongoDB-Test-CA"# 2. 生成服务端私钥和证书签名请求openssl genrsa-outmongod.key2048openssl req-new-keymongod.key-outmongod.csr\-subj"/CN=localhost"# 3. 用 CA 签发服务端证书openssl x509-req-inmongod.csr-CAca.crt-CAkeyca.key\-CAcreateserial-outmongod.crt-days365-sha256# 4. 合并证书和私钥为 PEM 文件catmongod.key mongod.crt>mongod.pemchmod600mongod.pem# 5. 启动 mongod 时指定证书# 在 docker-compose.yml 中增加:# command: --tlsMode requireTLS --tlsCertificateKeyFile /etc/ssl/mongod.pem --tlsCAFile /etc/ssl/ca.crt# 并将证书文件挂载进容器# 6. 客户端连接时指定 TLS# mongosh --tls --tlsCAFile ca.crt --host localhost注意:TLS 配置较复杂且环境的差异大,本章仅展示流程框架。生产环境强烈建议使用 MongoDB Atlas(云服务自动配 TLS)或让运维团队通过 Kubernetes Secret 管理证书。
步骤六:安全检查清单
目标:输出一份团队自检用的安全清单。
// MongoDB 安全自检脚本(管理员视角)use admin// 1. 检查是否开启认证constauthStatus=db.runCommand({getCmdLineOpts:1})print("认证状态:",JSON.stringify(authStatus.parsed?.security?.authorization||"未开启 ⚠"))// 2. 列出所有用户及其角色db.system.users.find({},{user:1,roles:1}).forEach(u=>{print(`用户:${u.user}, 角色:${JSON.stringify(u.roles)}`)})// 3. 检查 root 用户个数(应只有 1 个)constrootCount=db.system.users.countDocuments({"roles.role":"root"})print("Root 用户数:",rootCount,rootCount>2?"⚠ 过多":"PASS")// 4. 检查是否有空密码用户constnoPwd=db.system.users.find({"credentials.SCRAM-SHA-256":{$exists:false}}).count()print("弱认证账号:",noPwd,noPwd>0?"⚠ 存在":"PASS")3.3 完整代码清单
| 文件 | 用途 |
|---|---|
mongodb-lab/scripts/ch13-create-users.js | 创建分层账号体系的脚本 |
mongodb-lab/scripts/ch13-verify-permissions.js | 权限验证脚本 |
mongodb-lab/scripts/ch13-custom-role.js | 自定义角色演示 |
mongodb-lab/scripts/ch13-security-checklist.js | 安全自检脚本 |
3.4 测试验证
// 验证脚本(以 root 身份运行)use admin// 1. 确认业务账号仅能访问 local_lifeconsttestAuth=db.runCommand({usersInfo:{user:"app_life",db:"admin"},showPrivileges:false})print("app_life 角色:",JSON.stringify(testAuth.users[0].roles))// 应只含 {role:"readWrite", db:"local_life"}// 2. 确认监控账号不能写数据consttestMonitor=db.runCommand({usersInfo:{user:"monitor",db:"admin"}})constmonitorRoles=testMonitor.users[0].roles.map(r=>r.role)print("monitor 是否含有 data 角色:",monitorRoles.some(r=>r.includes("readWrite")||r.includes("root"))?"FAIL":"PASS")// 3. 确认测试账号已删除consttestTester=db.system.users.findOne({user:"tester_life"})print("tester_life 是否已清理:",testTester===null?"PASS":"FAIL")// 4. 清理(如需)// db.dropUser("app_life")// db.dropUser("report_reader")// db.dropUser("monitor")// db.dropUser("backup_user")// db.dropUser("product_admin")// db.runCommand({ dropRole: "productManager" })print("\n=== 安全性验证完成 ===")4. 项目总结
4.1 MongoDB 安全 vs MySQL 安全
| 维度 | MongoDB | MySQL | 说明 |
|---|---|---|---|
| 认证机制 | SCRAM-SHA-256(默认) | mysql_native_password / caching_sha2_password | 两者均支持强认证 |
| 角色管理 | 内置角色 + 自定义角色 | 全局权限 + 库级权限 | MongoDB 角色粒度更灵活 |
| 网络加密 | TLS(配置稍复杂) | TLS | 功能等价 |
| 默认安装安全 | 无认证(4.0 前) | 有初始密码(5.7+) | MySQL 近年改进更明显 |
| 审计日志 | 企业版功能 | 企业版 + 插件 | 社区版两者均无内置审计 |
| 字段级加密 | 客户端字段级加密(CSFLE) | 需应用层实现 | MongoDB 有原生方案 |
4.2 适用场景
MongoDB 安全配置适用:
- 生产环境所有 MongoDB 实例——必须开启认证和最小权限。
- 需要多团队协作的项目——不同账号不同权限,互不干扰。
- 数据合规要求(GDPR/PIPL)——传输加密 + 字段级加密 + 审计。
- 监控和备份系统的账号隔离——专用账号避免权限泄漏。
- CI/CD 管道中的临时数据库——动态创建账号并在管道结束时销毁。
不适用场景:
- 纯本机单机学习环境——开认证增加心智负担,但关闭网络端口。
- 只读的数据副本——如果不需要区分用户,可以用
--auth但配简单密码。
4.3 注意事项
| 注意事项 | 说明 |
|---|---|
| 认证库(authSource) | root 角色在 admin 库,业务用户在对应业务库,连接时必须正确指定 |
| 密码明文 | 配置文件中勿写明文密码,用环境变量或 Secret Manager(Vault/AWS Secrets Manager) |
匿名角色readAnyDatabase | 小心授予——它允许读所有数据库,含 system 集合 |
| 未加密的公网 MongoDB | 从 2017 年起已有数万起勒索攻击案例,永久不要公网裸奔 |
| TLS 证书过期 | 自签证书有效期设为定期提醒项,过期会导致所有连接中断 |
4.4 常见踩坑经验
故障案例一:authSource 配错导致连接失败
新来的运维在 Connection String 中写了mongodb://app_life:pwd@host:27017/local_life,没加?authSource=admin,结果连接报错 “Authentication failed”。根因:app_life是在 admin 库中创建的(root/admin 账号都是),而连接串默认以目标数据库local_life作为认证库。解决:连接串末尾加?authSource=admin,或在local_life库中直接创建app_life用户。
故障案例二:监控账号权限不足导致 Prometheus 数据缺失
团队创建了monitor用户并授予clusterMonitor,但 Prometheus 的mongodb_exporter请求db.serverStatus()正常,请求db.stats()却报 403。根因:clusterMonitor允许serverStatus但不允许dbStats(这是数据库级别的操作)。解决:追加{ role: "read", db: "local_life" }到 monitor 用户——有 read 权限就能执行dbStats。
故障案例三:密码泄露后未及时吊销导致数据外泄
某项目 GitHub 仓库中误提交了含 MongoDB 密码的application.yml,3 天后才发现。此时黑客已通过该账号全量导出了用户数据。根因:密码泄露后的响应太慢,且账号有全量readWrite权限。解决:事后应对——立即修改密码 + 检查审计日志;事前预防——使用 GitHub Secret Scanning +.gitignore明文配置文件 + 密码存入环境变量。
4.5 思考题
- 如果一个用户被授予了
readWrite在 database_A,又被授予了read在 database_B,他能跨库做$lookup从 database_A 关联 database_B 的数据吗? - MongoDB 的客户端字段级加密(CSFLE)是如何实现"数据库管理员也看不到明文"的?加密和解密在哪里发生?
(答案将在第 14 章末尾揭晓)
上一章思考题答案:
Spring Data MongoDB 的分页
Page<Product>底层使用的是skip + limit,而非游标分页。它在构造Query时自动加入.skip(page * size).limit(size),不涉及游标编码/解码。这也是为什么用 Spring Data 默认分页翻到深页性能会急剧下降——skip 机制的本质问题无法绕过。若需高性能深分页,必须手动实现游标分页(用 MongoTemplate 构造$gt/$lt条件)。Spring Boot 的读写分离配置:在
MongoClientSettings中设置readPreference(ReadPreference.secondary())。一致性陷阱——写后立刻读可能从 Secondary 读不到刚写的数据(未复制完成);Secondary 延迟较大时可能读到过期数据。对策:要求强一致性的读操作在MongoTemplate查询时临时覆写readPreference为primary(但会导致读写分离失效,该查询的流量回到主库)。
延伸阅读与资源
python入门:Rquests从菜鸟脚本到企业级SDK的网络实战圣经
Milvus向量数据库实战修炼:从 0 到 1精通向量检索与生产落地
后端工程师的 AI 转型第一课:Ollama 与私有化大模型实战
10倍开发者的 Dify 魔法书:从零构建全栈 AI 应用
后端工程师转型AI第一课-Ollama 与私有化大模型实战
大型语言模型(LLM) vLLM 高性能推理落地实战
Agent开发之LlamaIndex 实战修炼与源码进阶
大语言模型Transformers 实战修炼与源码剖析