MongoDB 的 SCRAM 认证握手到底几步、传了什么?服务端存了什么?授权不对时报什么错?这篇手写一遍握手来验证。
find 被拒: code 13 Unauthorized: not authorized on test to execute command { find: "t", filter: {}, lsid: { id: UUID("fb8f3d31-98b5-464b-ab2e-1624a4c475ed") }, $db: "test" }, full err
listCollections 被拒: code 13 Unauthorized: not authorized on test to execute command { listCollections: 1, cursor: {}, nameOnly: true, lsid: { id: UUID("fb8f3d31-98b5-464b-ab2e-1624a4c475ed") }
不需要认证的命令:ping → {'ok': 1.0};hello 里 saslSupportedMechs 需要先给 user 参数 → ['SCRAM-SHA-256', 'SCRAM-SHA-1']
localhost 例外:第一个用户可以不认证就创建: {'ok': 1.0}
创建第一个用户之后,再创建第二个用户(没认证): code 13 Unauthorized: Command createUser requires authentication, full error: {'ok': 0.0, 'errmsg': 'Command createUser requires authentication', 'code': 13, 'codeName': 'U--auth 下未认证的 find 是 code 13;还没有用户时本机连接可以创建第一个用户,此后创建用户也要认证。
顶层字段: ['_id', 'credentials', 'db', 'roles', 'user', 'userId'],_id = admin.alice,mechanisms = ['SCRAM-SHA-1', 'SCRAM-SHA-256']
SCRAM-SHA-1: iterationCount = 10000,salt 长度 = 16 字节,storedKey 长度 = 20 字节,serverKey 长度 = 20 字节
SCRAM-SHA-256: iterationCount = 15000,salt 长度 = 28 字节,storedKey 长度 = 32 字节,serverKey 长度 = 32 字节
roles = [{'role': 'read', 'db': 'test'}]
明文 "alicepw" 在文档里出现吗? False用户文档里只有每种机制的 salt、iterationCount、storedKey、serverKey,没有明文密码。
1. 客户端 → 服务器 saslStart:payload = "n,,n=alice,r=LC112qv6JyTl7Cq5l3TfRtv7"(mechanism = SCRAM-SHA-256)
服务器 → 客户端:conversationId = 1,done = False,payload = "r=<客户端nonce+服务端nonce, 56 字符>,s=30JbBf13…,i=15000"
2. 客户端 → 服务器 saslContinue:payload = "c=biws,r=…,p=<ClientProof 32 字节的 base64>"
服务器 → 客户端:done = True,payload = "v=<ServerSignature>"
自己算出的 storedKey == system.users 里的 storedKey: True;serverKey 相等: True;客户端校验服务器签名通过: True;握手共 2 条命令
认证后 connectionStatus: [{"user": "alice", "db": "admin"}]
不带 skipEmptyExchange:握手共 3 条命令(多一次空的 saslContinue)saslStart 发 n,,n=user,r=nonce,服务器回 salt 和 iterationCount;saslContinue 发 ClientProof,服务器回 ServerSignature。我们自己算出的 storedKey 与服务端一致。
密码错的 alice: code 18 AuthenticationFailed: Authentication failed., full error: {'ok': 0.0, 'errmsg': 'Authentication failed.', 'code': 18, 'codeName': 'AuthenticationFailed'}
不存在的用户 nobody: code 18 AuthenticationFailed: Authentication failed., full error: {'ok': 0.0, 'errmsg': 'Authentication failed.', 'code': 18, 'codeName': 'AuthenticationFailed'}
alice 在错误的 authSource(test): code 18 AuthenticationFailed: Authentication failed., full error: {'ok': 0.0, 'errmsg': 'Authentication failed.', 'code': 18, 'codeName': 'AuthenticationFailed'}
正确的 alice: {'ok': 1.0}
机制 PLAIN(服务端没开): code 334 MechanismUnavailable: Received authentication for mechanism PLAIN which is not enabled, full error: {'ok': 0.0, 'errmsg': 'Received authentication for mechanism PLAIN which密码错、用户不存在、authSource 不对都是同样的 code 18 Authentication failed.。
alice 读 test.t: []
alice 写 test.t: code 13 Unauthorized: not authorized on test to execute command { insert: "t", ordered: true, lsid: { id: UUID("ba75afdc-28c3-4e80-9548-026438f0a330") }, $db: "test" }, ful
alice 读 test2.t(没有授权的库): code 13 Unauthorized: not authorized on test2 to execute command { find: "t", filter: {}, lsid: { id: UUID("ba75afdc-28c3-4e80-9548-026438f0a330") }, $db: "test2" }, full e
connectionStatus 的 authenticatedUserRoles: [{"role": "read", "db": "test"}]
root 给 alice 加 readWrite@test 后,alice 的同一个连接写 test.t: 1
root 回收 readWrite@test 后,同一连接再写: code 13 Unauthorized: not authorized on test to execute command { insert: "t", ordered: true, lsid: { id: UUID("ba75afdc-28c3-4e80-9548-026438f0a330") }, $db: "test" }, ful只有 read 的用户写入是 code 13;grant/revoke 对同一个已认证连接立即生效。
不认证的 ping:1.1 ms;SCRAM-SHA-1:2.5 ms;SCRAM-SHA-256:3.4 ms (每次都是新建 MongoClient 加一次 ping;客户端和服务端在同一台机器上,没有网络延迟)
新建连接并认证:不认证 1.1 ms,SCRAM-SHA-1 2.5 ms,SCRAM-SHA-256 3.4 ms。