1.5. 安全性¶
在本文档中,我们将介绍 CouchDB 中的基本安全机制:基本身份验证 和 Cookie 身份验证。这是 CouchDB 处理用户并保护其凭据的方式。
1.5.1. 身份验证¶
CouchDB 具有一个管理员用户的概念(例如,管理员、超级用户或 root),该用户被允许对 CouchDB 安装执行任何操作。默认情况下,必须创建一个管理员用户才能使 CouchDB 成功启动。
CouchDB 还定义了一组只有管理员用户才能执行的请求。如果您已定义一个或多个特定管理员用户,CouchDB 将在某些请求时要求进行身份验证。
创建数据库 (
PUT /database
)删除数据库 (
DELETE /database
)设置数据库安全性 (
PUT /database/_security
)创建设计文档 (
PUT /database/_design/app
)更新设计文档 (
PUT /database/_design/app?rev=1-4E2
)触发压缩 (
POST /database/_compact
)读取任务状态列表 (
GET /_active_tasks
)在给定节点上重启服务器 (
POST /_node/{node-name}/_restart
)读取活动配置 (
GET /_node/{node-name}/_config
)
1.5.1.1. 创建新的管理员用户¶
如果您的安装过程未设置管理员用户,则必须手动将管理员用户添加到配置文件中,并首先重启 CouchDB。为了便于说明,我们将创建一个名为 admin
的默认用户,其密码为 password
。
警告
不要在没有思考的情况下直接输入以下内容!为您的管理员用户选择一个不容易猜到的好名字,并选择一个安全的密码。
在您的 etc/local.ini
文件末尾,在 [admins]
行之后,添加文本 admin = password
,使其看起来像这样
[admins]
admin = password
(不用担心密码以明文形式出现;我们稍后会解决这个问题。)
现在,使用适合您的操作系统的适当方法重启 CouchDB。您现在应该能够使用您的新管理员帐户访问 CouchDB。
> curl http://admin:password@127.0.0.1:5984/_up
{"status":"ok","seeds":{}}
太棒了!
让我们通过 HTTP API 创建一个管理员用户。我们将她命名为 anna
,她的密码是 secret
。请注意以下代码中的双引号;它们是用来表示 配置 API 的字符串值的。
> HOST="http://admin:[email protected]:5984"
> NODENAME="_local"
> curl -X PUT $HOST/_node/$NODENAME/_config/admins/anna -d '"secret"'
""
根据 _config API 的行为,我们正在获取刚刚写入的配置项的先前值。由于我们的管理员用户不存在,因此我们得到一个空字符串。
请注意,_local
充当本地节点名称的别名,因此对于所有配置 URL,NODENAME
可以设置为 _local
,以与本地节点的配置进行交互。
另请参见
1.5.1.1.1. 哈希密码¶
看到明文密码很可怕,不是吗?不用担心,CouchDB 不会在任何地方显示明文密码。它会立即被哈希化。现在,请查看您的 local.ini
文件。您会发现 CouchDB 已重写了明文密码,使其被哈希化。
[admins]
admin = -pbkdf2-71c01cb429088ac1a1e95f3482202622dc1e53fe,226701bece4ae0fc9a373a5e02bf5d07,10
anna = -pbkdf2-2d86831c82b440b8887169bd2eebb356821d621b,5e11b9a9228414ab92541beeeacbf125,10
哈希值是那个又大又丑又长的字符串,它以 -pbkdf2-
开头。
为了在身份验证期间将明文密码与存储的哈希值进行比较,会运行哈希算法,并将生成的哈希值与存储的哈希值进行比较。对于不同密码的两个相同哈希值的概率微不足道(参见 Bruce Schneier)。如果存储的哈希值落入攻击者手中,根据目前的标准,从哈希值中找到明文密码非常不方便(即,需要大量资金和时间)。
当 CouchDB 启动时,它会读取一组包含配置设置的 .ini
文件。它将这些设置加载到一个内部数据存储中(不是数据库)。配置 API 允许您读取当前配置,以及更改配置和创建新条目。CouchDB 会将任何更改写回 .ini
文件。
当 CouchDB 未运行时,也可以手动编辑 .ini
文件。与我们之前展示的创建管理员用户的方式不同,您可以停止 CouchDB,打开您的 local.ini
,在 admins
中添加 anna = secret
,然后重启 CouchDB。在从 local.ini
中读取新行时,CouchDB 会运行哈希算法并将哈希值写回 local.ini
,替换明文密码——就像它对我们最初的 admin
用户所做的那样。为了确保 CouchDB 仅对明文密码进行哈希化,而不是对现有哈希值进行二次哈希化,它会在哈希值前面加上 -pbkdf2-
,以区分明文密码和 PBKDF2 哈希密码。这意味着您的明文密码不能以字符 -pbkdf2-
开头,但这本来就很不可能发生。
1.5.1.2. 基本身份验证¶
除非我们提供正确的管理员用户凭据,否则 CouchDB 不会允许我们创建新数据库。让我们验证一下。
> HOST="http://127.0.0.1:5984"
> curl -X PUT $HOST/somedatabase
{"error":"unauthorized","reason":"You are not a server admin."}
看起来很正常。现在,我们使用正确的凭据再次尝试。
> HOST="http://anna:[email protected]:5984"
> curl -X PUT $HOST/somedatabase
{"ok":true}
如果您曾经访问过受密码保护的网站或 FTP 服务器,那么 username:password@
URL 变体应该看起来很熟悉。
如果您注重安全,那么在 http://
中缺少的 s
会让您感到不安。我们正在以明文形式将密码发送到 CouchDB。这是一件坏事,对吧?是的,但请考虑我们的场景:CouchDB 在我们作为唯一用户的开发机器上监听 127.0.0.1
。谁可能嗅探我们的密码呢?
但是,如果您处于生产环境中,则需要重新考虑。您的 CouchDB 实例会通过公共网络进行通信吗?即使是与其他托管客户共享的 LAN 也是公共的。有多种方法可以确保您或您的应用程序与 CouchDB 之间的通信安全,这超出了本文档的范围。从版本 1.1.0 开始,CouchDB 自带 内置 SSL。
另请参见
1.5.2. 身份验证数据库¶
您可能已经注意到 CouchDB 管理员是在配置文件中定义的,并且想知道普通用户是否也存储在那里。不,他们没有。CouchDB 拥有一个特殊的 身份验证数据库,默认情况下名为 _users
,它将所有注册用户存储为 JSON 文档。
此特殊数据库是一个 系统数据库。这意味着虽然它共享通用的 数据库 API,但会应用一些特殊的安全相关约束。以下是 身份验证数据库 与其他数据库的不同之处。
只有管理员可以浏览所有文档的列表 (
GET /_users/_all_docs
)只有管理员可以监听 更改馈送 (
GET /_users/_changes
)只有管理员可以执行设计函数,例如 视图。
有一个特殊的
_auth
设计文档,它不能被修改除了 设计文档 之外的每个文档都代表注册的 CouchDB 用户,并且属于他们
默认情况下,
_users
数据库的_security
设置禁止用户访问或修改文档
注意
可以更改设置,以便用户确实可以访问 _users
数据库,但即使那样,他们也只能访问 (GET /_users/org.couchdb.user:Jan
) 或修改 (PUT /_users/org.couchdb.user:Jan
) 他们拥有的文档。这在 CouchDB 4.0 中将不再可能。
这些严厉的规则是必要的,因为 CouchDB 关心其用户的个人信息,并且不会将其透露给任何人。通常,用户文档包含系统信息,例如 登录、密码哈希 和 角色,以及敏感的个人信息,例如真实姓名、电子邮件、电话、特殊内部标识等。这不是您想与世界分享的信息。
1.5.2.1. 用户文档¶
每个 CouchDB 用户都以文档格式存储。这些文档包含几个 CouchDB 用于身份验证的必需字段
_id (string): 文档 ID。包含用户的登录名,带有特殊前缀 为什么使用 org.couchdb.user: 前缀?
derived_key (string): 从盐/迭代派生的 PBKDF2 密钥。
name (string): 用户名,也称为登录名。不可变,例如,您不能重命名现有用户 - 您必须创建一个新的用户
roles (string 数组): 用户角色列表。CouchDB 不提供任何内置角色,因此您可以根据需要自由定义自己的角色。但是,您不能在那里设置系统角色,例如
_admin
。此外,只有管理员可以为用户分配角色 - 默认情况下,所有用户都没有角色password (string): 可以提供明文密码,但在实际存储文档之前,它将被哈希字段替换。
password_sha (string): 带有盐的哈希密码。用于
simple
password_schemepassword_scheme (string): 密码哈希方案。可以是
simple
或pbkdf2
salt (string): 哈希盐。用于
simple
和pbkdf2
password_scheme
选项。iterations (integer): 用于派生密钥的迭代次数,用于
pbkdf2
password_scheme
请参阅 配置 API:: 有关详细信息。type (string): 文档类型。始终具有值
user
此外,您还可以指定与目标用户相关的任何自定义字段。
1.5.2.1.1. 为什么使用 org.couchdb.user:
前缀?¶
在用户登录名之前使用特殊前缀的原因是为了拥有用户所属的命名空间。此前缀旨在防止在尝试合并两个或多个 _user 数据库时发生复制冲突。
对于当前的 CouchDB 版本,所有用户都属于同一个 org.couchdb.user
命名空间,并且无法更改。这可能会在将来的版本中更改。
1.5.2.2. 创建新用户¶
创建新用户是一个非常简单的操作。您只需要使用用户的 PUT 请求将数据发送到 CouchDB。让我们创建一个登录名为 jan、密码为 apple 的用户
curl -X PUT http://localhost:5984/_users/org.couchdb.user:jan \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"name": "jan", "password": "apple", "roles": [], "type": "user"}'
此 curl 命令将生成以下 HTTP 请求
PUT /_users/org.couchdb.user:jan HTTP/1.1
Accept: application/json
Content-Length: 62
Content-Type: application/json
Host: localhost:5984
User-Agent: curl/7.31.0
CouchDB 将以以下方式回复
HTTP/1.1 201 Created
Cache-Control: must-revalidate
Content-Length: 83
Content-Type: application/json
Date: Fri, 27 Sep 2013 07:33:28 GMT
ETag: "1-e0ebfb84005b920488fc7a8cc5470cc0"
Location: https://127.0.0.1:5984/_users/org.couchdb.user:jan
Server: CouchDB (Erlang OTP)
{"ok":true,"id":"org.couchdb.user:jan","rev":"1-e0ebfb84005b920488fc7a8cc5470cc0"}
文档已成功创建!用户 jan 现在应该存在于我们的数据库中。让我们检查一下是否属实
curl -X POST http://localhost:5984/_session -d 'name=jan&password=apple'
CouchDB 应该以以下方式回复
{"ok":true,"name":"jan","roles":[]}
这意味着用户名已识别,密码的哈希值与存储的哈希值匹配。如果我们指定了错误的登录名和/或密码,CouchDB 将以以下错误消息通知我们
{"error":"unauthorized","reason":"Name or password is incorrect."}
1.5.2.3. 更改密码¶
让我们从 CouchDB 和身份验证数据库的角度定义什么是更改密码。由于“用户”是“文档”,因此此操作只是使用包含明文密码的特殊字段 password
更新文档。害怕吗?不必害怕。身份验证数据库在文档更新时有一个特殊的内部钩子,它会查找此字段,并根据所选的 password_scheme
用安全哈希替换它。
总结上述过程 - 我们需要获取文档的内容,添加包含新密码的明文字段 password
,然后将 JSON 结果存储到身份验证数据库中。
curl -X GET http://localhost:5984/_users/org.couchdb.user:jan
{
"_id": "org.couchdb.user:jan",
"_rev": "1-e0ebfb84005b920488fc7a8cc5470cc0",
"derived_key": "e579375db0e0c6a6fc79cd9e36a36859f71575c3",
"iterations": 10,
"name": "jan",
"password_scheme": "pbkdf2",
"roles": [],
"salt": "1112283cf988a34f124200a050d308a1",
"type": "user"
}
这是我们的用户文档。我们可以从存储的文档中删除哈希值,以减少发布的数据量
curl -X PUT http://localhost:5984/_users/org.couchdb.user:jan \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "If-Match: 1-e0ebfb84005b920488fc7a8cc5470cc0" \
-d '{"name":"jan", "roles":[], "type":"user", "password":"orange"}'
{"ok":true,"id":"org.couchdb.user:jan","rev":"2-ed293d3a0ae09f0c624f10538ef33c6f"}
已更新!现在让我们检查一下密码是否真的已更改
curl -X POST http://localhost:5984/_session -d 'name=jan&password=apple'
CouchDB 应该以以下方式回复
{"error":"unauthorized","reason":"Name or password is incorrect."}
看起来密码 apple
是错误的,orange
呢?
curl -X POST http://localhost:5984/_session -d 'name=jan&password=orange'
CouchDB 应该以以下方式回复
{"ok":true,"name":"jan","roles":[]}
万岁!您可能想知道为什么这么复杂 - 我们需要检索用户的文档,向其中添加一个特殊字段,然后将其发布回去。
注意
API 请求没有密码确认:您应该在应用程序层实现它。