必须启用requireTLS模式并显式配置--tlsCAFile,否则X.509证书仅建立加密连接而无法完成身份鉴权;mongod启动时若仅设--tlsCertificateKeyFile和--tlsMode requireTLS但缺失--tlsCAFile,客户端证书将因缺乏可信根CA而被拒绝认证。

必须启用 requireTLS 模式并显式配置 CA 文件,否则 X.509 证书仅能建立加密连接,无法完成身份鉴权。
mongod 启动时必须指定 --tlsCAFile(不能只靠 --tlsCertificateKeyFile)
很多人误以为只要提供服务端证书(--tlsCertificateKeyFile)就能支持客户端证书验证,其实不行。MongoDB 要校验客户端证书签名是否可信,必须知道签发它的根 CA——也就是 --tlsCAFile 所指向的 PEM 文件。漏掉这个参数,即使客户端传了有效证书,mongod 也会直接拒绝认证,日志里通常只报 SSL peer certificate validation failed,不提示缺 CA。
常见错误配置:
- 只设
--tlsCertificateKeyFile和--tlsMode requireTLS,没设--tlsCAFile - 用了
--tlsAllowInvalidCertificates true,结果客户端证书被跳过校验,但后续$external用户查找失败 -
--tlsCAFile指向的是中间 CA 或 leaf 证书,而非根 CA 的 PEM(X.509 验证链必须从客户端证书一路回溯到根)
客户端证书 subject 必须精确映射为 $external 数据库中的用户
MongoDB 不解析证书里的 SAN(Subject Alternative Name),只认 subject 字段的完整字符串作为用户名。例如证书 subject 是 CN=app1,OU=services,O=acme,那必须在 admin 数据库中执行:
use $external
db.createUser({
user: "CN=app1,OU=services,O=acme",
roles: [{ role: "readWrite", db: "myapp" }]
})
注意几点:
- 用户名区分大小写,且空格、逗号、等号位置必须完全一致
- 不能省略
O=、OU=等字段,哪怕值为空(如CN=app1,,O=acme是非法 subject) - 该用户只能存在于
$external数据库,不能建在其他 DB 下 - 如果用副本集,所有节点的
mongod都要加载同一份--tlsCAFile,否则主节点能认、从节点拒绝同步
PyMongo 连接时 tlsCAFile 和 tlsCertificateKeyFile 缺一不可
客户端驱动不是“自动信任系统 CA”的行为。即使你本地系统已安装根证书,PyMongo 默认也不会读取它。必须显式传入:
-
tlsCAFile:和服务器端--tlsCAFile是同一个文件,用于验证服务器证书 -
tlsCertificateKeyFile:包含客户端私钥 + 客户端证书的 PEM 文件(顺序必须是 cert 在前、key 在后)
错误示例:
只传 tlsCertificateKeyFile 但不传 tlsCAFile → 报错 ssl.SSLError: [SSL: TLSV13_ALERT_CERTIFICATE_REQUIRED]
把 client key 单独拆成 tlsCertificateKeyFile 和 tlsKeyFile → PyMongo 会忽略 tlsKeyFile,静默失败
正确写法(连接字符串):
mongodb://localhost:27017/?tls=true&tlsCAFile=%2Fpath%2Fto%2Fca.pem&tlsCertificateKeyFile=%2Fpath%2Fto%2Fclient.pem
鉴权流程实际分两步:TLS 握手 + $external 用户匹配
这是最容易混淆的点。X.509 在 MongoDB 里不是“替代密码”的单一机制,而是两个阶段叠加:
- 第一阶段(TLS 层):用
--tlsCAFile验证客户端证书签名有效性,确认它由受信 CA 签发;这一步失败,连接直接断开 - 第二阶段(MongoDB 层):提取客户端证书
subject字符串,在$external数据库中查找同名用户;这一步失败,报错Authentication failed.,但 TLS 连接仍保持(只是无法执行任何命令)
所以看到连接成功但 db.runCommand({connectionStatus: 1}) 显示 "authInfo" : { "authenticatedUsers" : [] },基本就是第二步出问题——检查 subject 字符串是否拼写一致,以及用户是否真建在 $external 里。











