在数据库安全体系中,身份认证是第一道防线。MongoDB默认使用SCRAM(Salted Challenge Response Authentication Mechanism)作为账号密码的验证机制,从4.0版本开始,官方正式支持SCRAM-SHA-256并设为默认机制,相比早期的SCRAM-SHA-1,它在防字典攻击、防窃听、防服务端伪造等方面都有明显改进。理解这套机制的工作原理,不仅能帮助你在排障时快速定位认证失败的原因,也有助于正确配置集群间的内部认证与客户端访问控制。

一、SCRAM-SHA-256的基本原理
SCRAM的全称是Salted Challenge Response Authentication Mechanism,即带盐值的挑战-响应认证机制。它最核心的设计目标是:密码永远不以明文形式在网络上传输,服务端也只保存密码派生出来的密钥材料,而非密码本身。这样即使数据库文件被拖走,攻击者也无法直接还原出用户口令。
SCRAM-SHA-256的完整交互流程分为三个步骤。第一步,客户端发送client-first-message,携带用户名和一个随机生成的client nonce(一次性随机数)。第二步,服务端返回server-first-message,包含服务端自己的随机nonce、用户的盐值(salt)以及迭代次数(iterationCount),此时客户端拿到了派生密钥所需的全部参数。第三步,客户端使用PBKDF2算法,以密码和盐值、迭代次数计算出SaltedPassword,再依次派生出ClientKey和StoredKey,随后发送client-final-message(包含ClientProof)。服务端用自己保存的StoredKey验证这个证明,验证通过后再发送ServerSignature让客户端确认服务端身份,从而实现双向校验。
这套机制的安全性主要体现在三点:一是盐值和随机nonce保证了即使两个用户使用相同密码,存储的密钥也完全不同,且每次会话的挑战数据都不同,重放攻击无法得逞;二是PBKDF2的迭代次数可以调节(默认为15000次),大幅提升了暴力破解的计算成本;三是客户端通过验证ServerSignature确认服务端确实持有ServerKey,杜绝了中间人伪装成数据库服务器的可能。
二、密钥派生流程与存储结构详解
要真正理解SCRAM,关键是弄清楚几个密钥的派生关系。首先是SaltedPassword,它由PBKDF2(password, salt, iterationCount, 32)计算得出。然后ClientKey = HMAC(SaltedPassword, "Client Key"),而服务端真正存储的StoredKey = H(ClientKey),即ClientKey的SHA-256哈希值。最后ServerKey = HMAC(SaltedPassword, "Server Key")。可以看到,服务端数据库中保存的是SaltedPassword、StoredKey、ServerKey、salt和iterationCount这五类字段,而不保存任何可直接还原密码的信息。
认证时的核心验证逻辑如下:客户端计算AuthMessage(由client-first-message-bare、server-first-message、client-final-message-without-proof拼接而成),再计算ClientSignature = HMAC(StoredKey, AuthMessage),ClientProof = ClientKey XOR ClientSignature。服务端拿到ClientProof后,用它异或ClientSignature反推出ClientKey,然后对ClientKey做哈希并与本地存储的StoredKey比对,一致则认证通过。这个异或运算的设计非常巧妙,它让ClientKey本身不出现在网络报文中,同时服务端又足以完成验证。
我们可以在MongoDB中直接观察用户的认证机制和凭证存储情况:
use admin
// 查看指定用户的认证机制
db.getUser("appUser")["mechanisms"]
// 查询system.users集合中的凭证结构(需在配置了访问控制的实例上操作)
db.getSiblingDB("admin").system.users.find(
{ user: "appUser" },
{ user: 1, db: 1, mechanisms: 1, credentials: 1 }
)
// 返回结果中,SCRAM-SHA-256条目包含iterationCount、salt、
// storedKey、serverKey四个字段,没有明文密码注意,SCRAM-SHA-256与SCRAM-SHA-1的一个重要区别在于:SCRAM-SHA-1在计算密码哈希时需要对用户名和密码做MD5编码的国际化处理(SASLprep的前身),而SCRAM-SHA-256直接使用SASLprep规范化密码字符串,对包含中文或特殊字符的密码兼容性更好,也避免了某些字符集下认证异常的老问题。
三、服务端配置与用户创建实战
开启SCRAM-SHA-256认证非常简单,在mongod的配置文件中设置security.authorization为enabled即可,认证机制默认就包含SCRAM-SHA-256。配置文件示例如下:
# /etc/mongod.conf security: authorization: enabled # MongoDB 4.0+ 默认同时支持 SCRAM-SHA-1 和 SCRAM-SHA-256 # 如需只保留SHA-256,可显式设置: # setParameter: # authenticationMechanisms: SCRAM-SHA-256
配置完成后重启服务,首次需要通过localhost例外(localhost exception)创建管理员用户:
use admin
db.createUser({
user: "admin",
pwd: "强密码请自行替换",
roles: [ { role: "root", db: "admin" } ]
})
// 创建业务用户时指定认证机制
use testdb
db.createUser({
user: "appUser",
pwd: "应用密码请自行替换",
roles: [ { role: "readWrite", db: "testdb" } ],
mechanisms: [ "SCRAM-SHA-256" ]
})对于存量用户,如果原本只使用SCRAM-SHA-1,可以通过passwordDigester参数升级到双机制并存,实现平滑过渡:
use testdb
db.updateUser("appUser", {
mechanisms: [ "SCRAM-SHA-1", "SCRAM-SHA-256" ],
passwordDigester: "scram"
})
// 待所有客户端驱动升级到支持SHA-256的版本后,
// 再移除SCRAM-SHA-1:
db.updateUser("appUser", {
mechanisms: [ "SCRAM-SHA-256" ]
})这里有一个常见的坑需要注意:passwordDigester的可选值是scram和scram-sha-256。如果目标机制列表包含SCRAM-SHA-1,必须使用scram,否则updateUser会报错;反之纯SHA-256场景建议用scram-sha-256。升级前务必确认所有连接该账号的应用驱动版本足够新,否则客户端会在协商认证机制时直接失败。
四、客户端连接与常见认证故障排查
客户端侧一般不需要手动指定机制,驱动会通过SASL协商自动选择。以mongo shell和常见URI为例:
# mongo shell(旧版)显式指定机制
mongo --host 192.168.0.1 -u appUser -p --authenticationDatabase testdb \
--authenticationMechanism SCRAM-SHA-256
# mongosh / 驱动连接URI写法
mongosh "mongodb://appUser@192.168.0.1:27017/testdb?authSource=testdb"排障时最典型的报错是Authentication failed。遇到这类问题建议按顺序检查:第一,确认authSource是否正确,账号在哪个库创建就必须以哪个库作为认证库,这是最高频的错误原因;第二,确认mechanisms列表中是否包含客户端请求的机制;第三,如果密码包含非ASCII字符,检查旧版SCRAM-SHA-1是否触发SASLprep规范化问题,可改用SHA-256规避;第四,查看服务端日志中authentication的失败记录,日志会明确标注失败的机制类型和用户DN,比客户端报错信息详细得多。
在副本集和分片集群环境中,除了客户端认证外还有集群内部认证(keyFile或x.509),两者相互独立。keyFile本质上是内部SCRAM认证的共享密钥,而客户端访问依然走SCRAM-SHA-256流程,不要混淆。总体来说,SCRAM-SHA-256配置成本低、安全性扎实,除非有特殊的合规或旧系统兼容需求,建议所有新项目直接采用它作为唯一的密码认证机制,并配合TLS加密、最小权限角色一起构建完整的访问控制体系。
MongoDBSCRAM-SHA-256身份认证修改时间:2026-09-15 01:14:39