MongoDB 安全完全指南
第一部分:MongoDB 身份验证与授权核心概念
1.1 身份验证 vs. 授权
在深入代码之前,必须理清两个核心概念:
| 概念 | 英文 | 作用 | 类比 |
|---|---|---|---|
| 身份验证 | Authentication | 验证"你是谁" | 出示身份证件 |
| 授权 | Authorization | 决定"你能做什么" | 检查门票权限 |
MongoDB 默认不启用身份验证,这意味着任何能连接到服务器的用户都可以访问所有数据。这在生产环境中是极其危险的。
1.2 MongoDB 安全架构层次
┌─────────────────────────────────────────────┐
│ 应用层 (Application) │
├─────────────────────────────────────────────┤
│ 客户端字段级加密 (CSFLE) - 字段级加密 │
├─────────────────────────────────────────────┤
│ SCRAM / x.509 身份验证 (Authentication) │
├─────────────────────────────────────────────┤
│ 基于角色的访问控制 (RBAC) - 授权 │
├─────────────────────────────────────────────┤
│ TLS/SSL 传输加密 (Transport Encryption) │
└─────────────────────────────────────────────┘
第二部分:启用身份验证与创建管理员
2.1 配置文件启用认证
在 mongod.conf 配置文件中启用身份验证:
yaml
# /etc/mongod.conf
# MongoDB 配置文件示例
security:
# 启用授权:设置为 'enabled' 要求所有客户端进行身份验证
# 设置为 'disabled' 则允许匿名访问(仅用于开发/测试)
authorization: enabled
# 网络配置 - 绑定到特定IP而非全部接口
net:
bindIp: 127.0.0.1,192.168.1.100 # 本地 + 内网IP
port: 27017
# 可选:启用TLS/SSL(将在第四部分详细讲解)
# net:
# tls:
# mode: requireTLS
# certificateKeyFile: /etc/ssl/mongodb.pem
启用认证后重启服务:
bash
# 重启 MongoDB 服务使配置生效
sudo systemctl restart mongod
# 验证服务状态
sudo systemctl status mongod
# 检查 MongoDB 日志确认认证已启用
sudo tail -f /var/log/mongodb/mongod.log | grep "authorization"
2.2 创建管理员用户
重要 :必须在启用认证之前 或通过 localhost 例外 创建第一个管理员用户:
javascript
// 连接到 MongoDB(此时认证尚未启用或使用 localhost 例外)
// 命令: mongosh
// 1. 切换到 admin 数据库(认证数据库)
// 所有全局用户都存储在 admin 数据库中
use admin
// 2. 创建超级管理员用户
db.createUser({
user: "adminUser", // 用户名
pwd: "SecurePass123!", // 密码(生产环境使用强密码)
roles: [
{ role: "root", db: "admin" } // root 角色拥有所有权限
]
})
// 3. 创建数据库管理员(用于管理所有数据库)
db.createUser({
user: "dbAdmin",
pwd: "DbAdminPass456!",
roles: [
{ role: "userAdminAnyDatabase", db: "admin" }, // 管理所有数据库的用户
{ role: "dbAdminAnyDatabase", db: "admin" }, // 管理所有数据库
{ role: "readWriteAnyDatabase", db: "admin" }, // 读写所有数据库
{ role: "clusterAdmin", db: "admin" } // 集群管理(副本集/分片)
]
})
// 4. 查看已创建的用户
db.getUsers() // 列出 admin 数据库中的所有用户
2.3 认证方式连接
bash
# 方式1:命令行参数认证
mongosh --username adminUser --password SecurePass123! --authenticationDatabase admin
# 方式2:交互式输入密码(更安全,密码不会显示在历史记录中)
mongosh --username adminUser --authenticationDatabase admin
# 然后输入密码
# 方式3:连接字符串 URI
mongosh "mongodb://adminUser:SecurePass123!@localhost:27017/admin?authSource=admin"
# 方式4:先连接再认证
mongosh
use admin
db.auth("adminUser", "SecurePass123!")
第三部分:基于角色的访问控制 (RBAC)
3.1 内置角色详解
MongoDB 提供丰富的内置角色,分为以下几类:
| 角色分类 | 角色名称 | 权限范围 | 典型用途 |
|---|---|---|---|
| 数据库用户角色 | read |
读取指定数据库的所有非系统集合 | 只读报表用户 |
readWrite |
读写指定数据库 | 应用普通用户 | |
| 数据库管理角色 | dbAdmin |
索引管理、统计信息、schema验证 | DBA操作 |
dbOwner |
dbAdmin + readWrite + userAdmin | 数据库完全控制 | |
userAdmin |
管理当前数据库的用户和角色 | 用户管理员 | |
| 集群管理角色 | clusterAdmin |
副本集、分片、集群操作 | 运维人员 |
clusterManager |
监控和管理集群 | 监控系统 | |
| 备份恢复角色 | backup |
备份数据 | 备份系统 |
restore |
恢复数据 | 恢复系统 | |
| 跨数据库角色 | readAnyDatabase |
所有数据库的读权限 | 审计/报表 |
readWriteAnyDatabase |
所有数据库的读写权限 | 迁移工具 | |
userAdminAnyDatabase |
管理所有数据库的用户 | 超级用户管理员 | |
| 超级用户 | root |
除 validate 外的所有权限 |
系统管理员 |
3.2 创建业务数据库用户
javascript
// ============================================
// 示例:为电商应用创建用户
// ============================================
// 1. 切换到目标数据库
use ecommerce
// 2. 创建应用程序读写用户
db.createUser({
user: "app_user",
pwd: "AppUserPass789!",
roles: [
{ role: "readWrite", db: "ecommerce" } // 对 ecommerce 数据库有读写权限
]
})
// 3. 创建只读报表用户
db.createUser({
user: "report_user",
pwd: "ReportUserPass321!",
roles: [
{ role: "read", db: "ecommerce" } // 只能读取,不能写入/修改
]
})
// 4. 创建数据库管理员(特定数据库)
db.createUser({
user: "ecommerce_admin",
pwd: "EcomAdminPass555!",
roles: [
{ role: "dbAdmin", db: "ecommerce" }, // 管理索引和统计
{ role: "readWrite", db: "ecommerce" }, // 读写数据
{ role: "userAdmin", db: "ecommerce" } // 管理 ecommerce 的用户
]
})
3.3 创建自定义角色
当内置角色无法满足精细权限需求时,创建自定义角色:
javascript
// ============================================
// 示例:创建只能操作 orders 集合的自定义角色
// ============================================
use ecommerce
// 1. 创建自定义角色
db.createRole({
role: "order_processor", // 角色名称
privileges: [
{
// 资源:只针对 orders 集合
resource: { db: "ecommerce", collection: "orders" },
// 允许的操作:查询、插入、更新(不允许删除)
actions: ["find", "insert", "update"]
},
{
// 资源:针对 products 集合只读
resource: { db: "ecommerce", collection: "products" },
actions: ["find"] // 只能查询产品,不能修改
}
],
roles: [] // 继承的角色(此处为空,不继承任何角色)
})
// 2. 查看创建的角色
db.getRole("order_processor", { showPrivileges: true })
// 3. 将自定义角色分配给用户
db.createUser({
user: "order_clerk",
pwd: "ClerkPass999!",
roles: [
{ role: "order_processor", db: "ecommerce" }
]
})
// 4. 测试权限
// 以 order_clerk 登录后测试:
// db.orders.find() // ✅ 允许
// db.orders.insert({}) // ✅ 允许
// db.orders.update(...) // ✅ 允许
// db.orders.remove({}) // ❌ 拒绝
// db.products.find() // ✅ 允许
// db.products.update() // ❌ 拒绝
3.4 用户管理操作
javascript
// ============================================
// 用户维护常用操作
// ============================================
use admin // 切换到管理数据库
// 1. 查看所有用户
db.getUsers()
// 或查看特定用户
db.getUser("app_user")
// 2. 修改用户密码
db.changeUserPassword("app_user", "NewStrongPassword789!")
// 3. 修改用户角色
db.updateUser("app_user", {
roles: [
{ role: "readWrite", db: "ecommerce" },
{ role: "read", db: "analytics" } // 增加对 analytics 的读权限
]
})
// 4. 为用户授予额外角色
db.grantRolesToUser("app_user", [
{ role: "read", db: "logs" } // 增加日志读取权限
])
// 5. 撤销用户角色
db.revokeRolesFromUser("app_user", [
{ role: "read", db: "logs" } // 移除日志读取权限
])
// 6. 删除用户
db.dropUser("report_user")
// 7. 查看当前用户的权限
db.runCommand({ usersInfo: "app_user", showPrivileges: true })
3.5 角色管理操作
javascript
// ============================================
// 角色维护操作
// ============================================
use ecommerce
// 1. 查看所有角色
db.getRoles()
// 查看角色详情(包含权限)
db.getRole("order_processor", { showPrivileges: true })
// 2. 更新角色权限
db.updateRole("order_processor", {
privileges: [
{
resource: { db: "ecommerce", collection: "orders" },
actions: ["find", "insert", "update", "delete"] // 增加删除权限
}
],
roles: [{ role: "read", db: "ecommerce" }] // 增加继承角色
})
// 3. 为角色授予额外权限
db.grantPrivilegesToRole("order_processor", [
{
resource: { db: "ecommerce", collection: "returns" },
actions: ["find", "insert", "update"]
}
])
// 4. 撤销角色权限
db.revokePrivilegesFromRole("order_processor", [
{
resource: { db: "ecommerce", collection: "orders" },
actions: ["delete"] // 撤销删除权限
}
])
// 5. 删除角色
db.dropRole("order_processor")
第四部分:传输层加密 (TLS/SSL)
4.1 生成证书(使用 OpenSSL)
对于生产环境,建议使用正式 CA 签发的证书。测试环境可自建 CA:
bash
# ============================================
# 步骤1:生成自签名根CA证书
# ============================================
# 生成根CA私钥(2048位RSA)
openssl genrsa -out ca-key.pem 2048
# 生成根CA证书(有效期3650天=10年)
openssl req -new -x509 -days 3650 -key ca-key.pem -out ca-cert.pem \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=MyRootCA"
# ============================================
# 步骤2:为 MongoDB 服务器生成证书
# ============================================
# 生成服务器私钥
openssl genrsa -out mongodb-key.pem 2048
# 生成证书签名请求 (CSR)
# 重要:CN (Common Name) 必须与 MongoDB 服务器主机名匹配
openssl req -new -key mongodb-key.pem -out mongodb.csr \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=server.example.com"
# 使用根CA签署服务器证书
openssl x509 -req -in mongodb.csr -CA ca-cert.pem -CAkey ca-key.pem \
-CAcreateserial -out mongodb-cert.pem -days 365
# 合并证书和私钥为 MongoDB 所需的 PEM 文件
cat mongodb-cert.pem mongodb-key.pem > mongodb.pem
# ============================================
# 步骤3:设置文件权限(重要!)
# ============================================
# 私钥文件必须设置为 600 权限
chmod 600 mongodb.pem
chmod 600 ca-key.pem
# 验证证书内容
openssl verify -CAfile ca-cert.pem mongodb-cert.pem
4.2 配置 MongoDB 启用 TLS
yaml
# /etc/mongod.conf
# TLS/SSL 配置部分
net:
port: 27017
bindIp: 0.0.0.0
tls:
# TLS 模式选项:
# - disabled: 禁用 TLS(默认)
# - allowTLS: 同时接受 TLS 和非 TLS 连接
# - preferTLS: 优先使用 TLS
# - requireTLS: 只接受 TLS 连接(推荐生产环境)
mode: requireTLS
# 服务器证书和私钥合并文件路径
certificateKeyFile: /etc/ssl/mongodb.pem
# 证书密码(如果私钥有加密)
# certificateKeyFilePassword: "password"
# CA 证书文件(用于验证客户端证书)
# 启用客户端证书验证时使用
CAFile: /etc/ssl/ca-cert.pem
# 是否允许无效主机名(仅测试环境)
# allowInvalidHostnames: false
# 是否允许过期证书(仅测试环境)
# allowInvalidCertificates: false
# 是否禁用 OCSP(在线证书状态协议)
# disabledProtocols: noTLS1_0
4.3 使用 TLS 连接 MongoDB
bash
# ============================================
# 方式1:mongosh 命令行连接
# ============================================
# 仅使用 TLS(不验证客户端证书)
mongosh --tls --tlsCAFile /etc/ssl/ca-cert.pem \
--host server.example.com \
--username adminUser \
--authenticationDatabase admin
# 使用客户端证书进行双向 TLS (mTLS) 认证
mongosh --tls \
--tlsCAFile /etc/ssl/ca-cert.pem \
--tlsCertificateKeyFile /etc/ssl/client.pem \
--host server.example.com
# ============================================
# 方式2:连接字符串 URI
# ============================================
# TLS 连接 URI 格式
mongodb://adminUser:password@server.example.com:27017/admin?tls=true&tlsCAFile=/etc/ssl/ca-cert.pem
# ============================================
# 方式3:应用程序驱动连接 (Node.js 示例)
# ============================================
javascript
// Node.js 驱动 TLS 连接示例
const { MongoClient } = require('mongodb');
const fs = require('fs');
// 配置 TLS 选项
const tlsOptions = {
// 启用 TLS
tls: true,
// CA 证书(用于验证服务器证书)
tlsCAFile: '/etc/ssl/ca-cert.pem',
// 客户端证书和私钥(用于 mTLS)
tlsCertificateKeyFile: '/etc/ssl/client.pem',
// 允许无效主机名(仅测试环境)
// tlsAllowInvalidHostnames: true,
// 允许无效证书(仅测试环境)
// tlsAllowInvalidCertificates: true,
};
// 连接 URI
const uri = 'mongodb://server.example.com:27017/admin';
// 创建客户端
const client = new MongoClient(uri, tlsOptions);
async function connect() {
try {
await client.connect();
console.log('TLS 连接成功!');
const db = client.db('ecommerce');
const result = await db.command({ ping: 1 });
console.log('数据库响应:', result);
} catch (err) {
console.error('连接失败:', err);
} finally {
await client.close();
}
}
connect();
python
# Python (pymongo) 驱动 TLS 连接示例
from pymongo import MongoClient
from pymongo.server_api import ServerApi
# 配置 TLS 选项
client = MongoClient(
'mongodb://server.example.com:27017/',
tls=True,
tlsCAFile='/etc/ssl/ca-cert.pem', # CA 证书
tlsCertificateKeyFile='/etc/ssl/client.pem', # 客户端证书
username='adminUser',
password='SecurePass123!',
authSource='admin',
server_api=ServerApi('1')
)
# 测试连接
try:
client.admin.command('ping')
print("TLS 连接成功!")
except Exception as e:
print(f"连接失败: {e}")
4.4 副本集 TLS 配置
yaml
# 副本集每个成员的配置
# /etc/mongod.conf
net:
port: 27017
bindIp: 0.0.0.0
tls:
mode: requireTLS
certificateKeyFile: /etc/ssl/mongodb.pem
CAFile: /etc/ssl/ca-cert.pem
# 集群内部通信也需要 TLS
clusterFile: /etc/ssl/cluster.pem # 集群成员间认证的证书
security:
# 副本集需要 keyFile 或 x.509 认证
keyFile: /etc/mongodb-keyfile
authorization: enabled
replication:
replSetName: "rs0"
副本集成员间认证 KeyFile 生成:
bash
# 生成 6-1024 个 Base64 字符的密钥文件
openssl rand -base64 756 > /etc/mongodb-keyfile
# 设置正确的权限(必须 400 或 600)
chmod 400 /etc/mongodb-keyfile
# 设置所有者
chown mongodb:mongodb /etc/mongodb-keyfile
# 将此文件复制到副本集所有成员
scp /etc/mongodb-keyfile user@member2:/etc/
第五部分:高级安全功能
5.1 客户端字段级加密 (CSFLE)
CSFLE 允许在客户端加密敏感字段,数据库只存储密文:
javascript
// ============================================
// 客户端字段级加密 - Node.js 示例
// ============================================
const { MongoClient } = require('mongodb');
const { ClientEncryption } = require('mongodb-client-encryption');
// 1. 配置加密选项
const autoEncryptionOptions = {
// 密钥保管库命名空间
keyVaultNamespace: 'encryption.__keyVault',
// KMS 提供者配置(使用本地 KMS 进行演示)
kmsProviders: {
local: {
// 96 字节的本地主密钥(从环境变量或安全存储获取)
key: Buffer.from(process.env.LOCAL_MASTER_KEY, 'base64')
}
},
// 加密模式映射
schemaMap: {
'ecommerce.users': {
bsonType: 'object',
properties: {
ssn: {
encrypt: {
bsonType: 'string',
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Random',
}
},
creditCard: {
encrypt: {
bsonType: 'string',
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic',
}
}
}
}
}
};
// 2. 创建带加密的客户端
const client = new MongoClient('mongodb://localhost:27017', {
autoEncryption: autoEncryptionOptions
});
async function encryptData() {
await client.connect();
const collection = client.db('ecommerce').collection('users');
// 3. 插入数据 - ssn 和 creditCard 字段会被自动加密
await collection.insertOne({
name: '张三',
ssn: '123-45-6789', // 加密存储
creditCard: '4111-1111-1111-1111', // 加密存储
email: 'zhang@example.com' // 明文存储
});
// 4. 查询加密字段(确定性加密支持精确查询)
const user = await collection.findOne({
creditCard: '4111-1111-1111-1111' // 自动加密查询条件
});
console.log('解密后的数据:', user);
// 输出: { name: '张三', ssn: '123-45-6789', creditCard: '4111-1111-1111-1111', ... }
}
5.2 可查询加密 (Queryable Encryption)
MongoDB 7.0+ 支持可查询加密,允许对加密数据执行范围查询:
javascript
// ============================================
// 可查询加密配置(MongoDB 7.0+)
// ============================================
// 定义加密模式
const encryptedFieldsMap = {
'ecommerce.patients': {
fields: [
{
path: 'ssn',
bsonType: 'string',
queries: { queryType: 'equality' } // 支持等值查询
},
{
path: 'age',
bsonType: 'int',
queries: { queryType: 'range' } // 支持范围查询
},
{
path: 'diagnosis',
bsonType: 'string'
// 无 queries 字段:不能查询,只能解密读取
}
]
}
};
// 配置自动加密
const autoEncryptionOptions = {
keyVaultNamespace: 'encryption.__keyVault',
kmsProviders: { local: { key: masterKey } },
encryptedFieldsMap: encryptedFieldsMap
};
// 使用示例
async function queryEncryptedData() {
const client = new MongoClient(uri, { autoEncryption: autoEncryptionOptions });
await client.connect();
const patients = client.db('ecommerce').collection('patients');
// 等值查询 - 支持
const patient = await patients.findOne({ ssn: '123-45-6789' });
// 范围查询 - 支持
const adults = await patients.find({
age: { $gte: 18, $lte: 65 }
}).toArray();
// 注意:被查询的字段必须配置相应的 queryType
}
5.3 审计日志
javascript
// ============================================
// 配置审计日志(企业版功能)
// ============================================
// mongod.conf 审计配置
/*
auditLog:
destination: file
format: JSON
path: /var/log/mongodb/audit.json
filter: '{ "atype": { $in: ["authCheck", "createUser", "dropUser", "grantRoles"] } }'
*/
// 审计日志输出示例
// {"atype":"authCheck","ts":{"$date":"2024-01-15T10:30:00Z"},"local":{"ip":"192.168.1.100","port":27017},"remote":{"ip":"192.168.1.50","port":54321},"users":[{"user":"app_user","db":"ecommerce"}],"roles":[{"role":"readWrite","db":"ecommerce"}],"param":{"command":"find","ns":"ecommerce.orders","args":{"filter":{"status":"pending"}}},"result":0}
第六部分:安全最佳实践清单
6.1 部署前检查清单
bash
# ============================================
# MongoDB 安全加固检查脚本
# ============================================
#!/bin/bash
echo "=== MongoDB 安全检查 ==="
# 1. 检查认证是否启用
if mongosh --eval "db.runCommand({connectionStatus:1})" 2>/dev/null | grep -q "authenticated";
then
echo "✅ 认证已启用"
else
echo "❌ 警告:认证未启用!"
fi
# 2. 检查绑定IP
BIND_IP=$(grep -E "^ bindIp:" /etc/mongod.conf | awk '{print $2}')
if [[ "$BIND_IP" == "0.0.0.0" ]] || [[ "$BIND_IP" == "*" ]]; then
echo "⚠️ 注意:MongoDB 绑定到所有接口,请确保防火墙已配置"
else
echo "✅ 绑定IP配置: $BIND_IP"
fi
# 3. 检查 TLS 配置
if grep -q "mode: requireTLS" /etc/mongod.conf; then
echo "✅ TLS 已启用并要求所有连接使用 TLS"
else
echo "⚠️ 建议:启用 TLS 加密传输"
fi
# 4. 检查文件权限
for file in /etc/mongod.conf /etc/mongodb-keyfile /etc/ssl/mongodb.pem; do
if [ -f "$file" ]; then
PERM=$(stat -c "%a" "$file")
if [ "$PERM" = "600" ] || [ "$PERM" = "400" ]; then
echo "✅ $file 权限正确 ($PERM)"
else
echo "⚠️ $file 权限应为600,当前为$PERM"
fi
fi
done
# 5. 检查是否禁用 HTTP 状态接口
if grep -q "http:" /etc/mongod.conf; then
echo "⚠️ 建议:禁用 HTTP 状态接口"
else
echo "✅ HTTP 状态接口已禁用"
fi
6.2 用户角色安全建议
| 原则 | 说明 | 反例 |
|---|---|---|
| 最小权限 | 只授予完成任务所需的最小权限 | 为报表用户授予 readWrite 而非 read |
| 独立账户 | 不同应用使用不同数据库账户 | 所有应用共享同一个数据库用户 |
| 定期轮换 | 定期更换密码和密钥 | 密码3年未更换 |
| 审计追踪 | 记录敏感操作日志 | 没有审计日志配置 |
| 网络隔离 | 使用防火墙限制访问来源 | 数据库暴露在公网 |
6.3 监控与告警配置
javascript
// ============================================
// 创建监控用户(只读)
// ============================================
use admin
db.createUser({
user: "monitoring_user",
pwd: "MonitorPass123!",
roles: [
{ role: "clusterMonitor", db: "admin" }, // 监控集群状态
{ role: "read", db: "local" } // 读取 oplog 信息
]
})
// 使用监控用户执行检查
// mongosh --username monitoring_user --authenticationDatabase admin
// 查看当前连接用户
db.currentOp(true)
// 查看失败的登录尝试
db.runCommand({ getLog: "global" }).log.filter(l => l.includes("Failed to authenticate"))
总结
本指南涵盖了 MongoDB 安全的三个核心层面:
- 身份验证 - 通过 SCRAM-SHA-256 或 x.509 证书验证用户身份
- 授权 - 基于 RBAC 模型,使用内置或自定义角色精细控制权限
- 传输加密 - 使用 TLS/SSL 保护网络通信,防止窃听和中间人攻击
快速参考命令:
bash
# 启用认证
security:
authorization: enabled
# 创建管理员
use admin; db.createUser({user:"admin",pwd:"pass",roles:["root"]})
# 创建应用用户
use mydb; db.createUser({user:"app",pwd:"pass",roles:["readWrite"]})
# TLS 连接
mongosh --tls --tlsCAFile ca.pem --host myhost
# 查看用户权限
db.getUser("username", {showPrivileges:true})