有没有办法以编程方式生成 CouchDB cookie?
Is there any way to programmatically generate a CouchDB cookie?
我正在开发一个应用程序,它将使用 CouchDB 为用户存储一些数据。但我不希望用户直接登录 CouchDB。
我将有一个应用程序客户端 (mobile/web)、一个应用程序服务器和 CouchDB 服务器。客户端应用程序将向应用程序服务器进行身份验证,然后我的理想情况是我的应用程序服务器以编程方式对用户进行身份验证,然后仅将 10 分钟的 cookie 发送到客户端应用程序。
也就是说,我希望应用服务器代表应用客户端的用户向CouchDB 服务器请求一个Cookie,然后只将cookie 发送给应用客户端。
应用程序服务器可以代表经过身份验证的用户POST到_session,但这需要:
- 在应用服务器中维护用户密码列表
- 对所有用户使用一个已知的密码
- 为每个身份验证请求将密码重置为随机密码
出于安全原因,#3 似乎是最好的,但这似乎是额外的工作,并且是到数据库的额外往返(尽管并不昂贵)。所以我的问题是:作为管理员,有没有什么方法可以代表用户生成 cookie,而根本不使用用户的密码?
作为附加的安全措施,这也可能允许我完全拒绝对 _session 的请求,但来自我的应用程序服务器的请求除外。
为了完整起见,我还要提到我已经查看了这些其他选项,发现它们需要:
-
x_auth_token 永不过期这一事实让我很担心。这意味着泄露的令牌将永远授予对用户数据的访问权限。而且 AFAICT,如果不更改用户名或服务器机密(这实际上也会使其他所有人的身份验证令牌无效),则令牌甚至无法失效。但也许我在这里遗漏了什么?
-
这好像只是搬家的问题。现在,我必须存储 OAuth 机密,而不是将用户密码存储在我的服务器应用程序中。另外,现在我的服务器和客户端代码肯定更复杂了。
我不遵循你的确切目标。您似乎暗示用户可能有密码 ("app server authenticating the users programmatically") 但您不希望用户 "ever need to know their CouchDB password"。您想要哪种身份验证?
我使用了两种(半)通用方法来使用 CouchDB 进行身份验证:
"Man-in-the-middle[ware]" 方法,我在 CouchDB 前面有瘦中间件。此中间件将 username/password 转发到“/_session”,它会根据 CouchDB _users 数据库生成 cookie 或错误代码。中间件将这个 cookie 从 CouchDB 复制到它自己的 HTTP 响应中返回给客户端(或在出现错误时显示一条消息)。然后在需要数据库访问的后续请求中,它将 cookie(现在来自客户端请求)再次转发回数据库。
传统方法,您只需使用 CouchDB 作为数据存储并维护自己的 "user" entries/indexes。确保您使用密码 storage/handling 的当前最佳做法或使用为您处理这些细节的库。中间件作为 "itself" 连接到数据库,并根据自己的 session 处理使用自己的逻辑处理 read/write 权限。
或者 — 某种混合方法 — 您可以使用“/_session”API only 来查看是否CouchDB 接受用户名+密码为有效。如果是,请为该用户创建一个单独的 middleware-handled session。 (基本上你只使用 CouchDB 的 _user 数据库作为 "password handling library",其余的是传统方法,其中访问控制全部在中间件而不是在数据库中实现。)
对于 real-world 生产资料,我倾向于只使用后两种(或 one-and-a-half 考虑到较早的编号……)——第一种方法很有趣,但 CouchDB 缺乏document-level 读取权限通常意味着授予用户 nearly-direct 访问数据库服务器的权限在实践中是站不住脚的。
更新:你的问题现在清楚地表明你希望客户端应用程序直接与两个服务器对话:应用程序(以前"middleware") 服务器 和 CouchDB(数据库)服务器。我保留上面的内容,因为我认为它仍然有些用处,并为这次更新提供了一点background/context。
您怀疑 Proxy Authentication 是错误的解决方案是正确的:它 不是 用于 end-user 用法,但实际上是为了替换 cookie-forwarding "trick" 以上#1 的部分。也就是说,代理身份验证是当您完全信任一个方(即您的中间软件)在其工作时提供用户信息代表 个用户。但是您希望用户 直接 与数据库对话,并且您不能通过 X-Auth-CouchDB-Token
.
信任他们
我会听从您对 OAuth 选项的判断。我确实认为它更接近你想要的,但很明显,你正在以某种方式对不同服务的用户进行身份验证,并且不需要存储 per-user keys in CouchDB 本身。 OAuth 1.0 要求的请求签名意味着您也需要客户端应用程序的 HTTP 库的支持。
我看到了一些选项,无需构建自定义 CouchDB 插件,它们可以让您的 app 服务器向经过身份验证的用户分发令牌,您的 database 服务器将接受:
毕竟代理!也就是说,将您的数据库服务器隐藏在您的应用程序服务器或另一个轻量级自定义 reverse-proxy 后面。所有这些中间件需要做的就是检查您现有的客户端应用程序 session(cookie 或其他身份验证 header),如果它有效,则设置 CouchDB 将接受的内部 proxy auth headers——然后它转发其余 request/response 逐字记录。
确定性密码,per-user如果它让你感觉更好。使用只有它知道的秘密配置您的应用服务器,然后将每个用户密码设置为 HMAC(username, app_server_secret)
之类的内容。现在,当您想为用户生成令牌时,您的应用服务器可以在 per-user 的基础上生成密码。请注意,这并不比仅使用 app_server_secret
作为每个用户的密码更安全——CouchDB 已经独立地对每个用户密码进行加盐和哈希处理,因此如果有人获得了数据库而不是您应用程序的配置值攻击者无法区分两者。在这两种情况下,防止未经授权的数据库使用完全取决于 app_server_secret
保密。
Re-implement CouchDB 当前的 cookie 生成算法。 CouchDB 的 cookie 算法(view source) is basically data = username + ':' + timestamp; base64(data + ':' + sha_mac(data, secret))
. Where secret
is the couch_httpd_auth.secret
value plus the user's salt
value. You can tell your app server the couchdb_httpd_auth/secret 值,它可以按照相同的步骤生成您提供给客户端应用程序的有效 cookie,CouchDB 将接受它作为自己的。此 cookie 将在时间戳 + 配置之前有效couch_httpd_auth/timeout。看起来 "hacky",这可能是最接近您要求的,尽管您仍然需要以某种方式 set/disable 用户的实际密码。
在 上扩展。我遇到了类似的问题,如果没有偶然发现那个答案,我永远不会意识到选项 3 是可能的。
这是我的 python3 生成 cookie 的实现(使用 pycouchdb 与沙发交互):
def generate_couchdb_cookie(couchAddress, couchSecret, username):
timestamp = format(int(time.time()), 'X')
data = username + ":" + timestamp
server = pycouchdb.Server(couchAddress)
db = server.database("_users")
doc = db.get("org.couchdb.user:" + username)
salt = doc["salt"]
secret = couchSecret + salt
hashed = hmac.new(secret.encode(), data.encode(), hashlib.sha1).digest()
inbytes = data.encode() + ":".encode() + hashed
result = base64.urlsafe_b64encode(inbytes)
return "AuthSession=" + (result.decode("utf-8")).rstrip('=')
我正在开发一个应用程序,它将使用 CouchDB 为用户存储一些数据。但我不希望用户直接登录 CouchDB。
我将有一个应用程序客户端 (mobile/web)、一个应用程序服务器和 CouchDB 服务器。客户端应用程序将向应用程序服务器进行身份验证,然后我的理想情况是我的应用程序服务器以编程方式对用户进行身份验证,然后仅将 10 分钟的 cookie 发送到客户端应用程序。
也就是说,我希望应用服务器代表应用客户端的用户向CouchDB 服务器请求一个Cookie,然后只将cookie 发送给应用客户端。
应用程序服务器可以代表经过身份验证的用户POST到_session,但这需要:
- 在应用服务器中维护用户密码列表
- 对所有用户使用一个已知的密码
- 为每个身份验证请求将密码重置为随机密码
出于安全原因,#3 似乎是最好的,但这似乎是额外的工作,并且是到数据库的额外往返(尽管并不昂贵)。所以我的问题是:作为管理员,有没有什么方法可以代表用户生成 cookie,而根本不使用用户的密码?
作为附加的安全措施,这也可能允许我完全拒绝对 _session 的请求,但来自我的应用程序服务器的请求除外。
为了完整起见,我还要提到我已经查看了这些其他选项,发现它们需要:
-
x_auth_token 永不过期这一事实让我很担心。这意味着泄露的令牌将永远授予对用户数据的访问权限。而且 AFAICT,如果不更改用户名或服务器机密(这实际上也会使其他所有人的身份验证令牌无效),则令牌甚至无法失效。但也许我在这里遗漏了什么?
-
这好像只是搬家的问题。现在,我必须存储 OAuth 机密,而不是将用户密码存储在我的服务器应用程序中。另外,现在我的服务器和客户端代码肯定更复杂了。
我不遵循你的确切目标。您似乎暗示用户可能有密码 ("app server authenticating the users programmatically") 但您不希望用户 "ever need to know their CouchDB password"。您想要哪种身份验证?
我使用了两种(半)通用方法来使用 CouchDB 进行身份验证:
"Man-in-the-middle[ware]" 方法,我在 CouchDB 前面有瘦中间件。此中间件将 username/password 转发到“/_session”,它会根据 CouchDB _users 数据库生成 cookie 或错误代码。中间件将这个 cookie 从 CouchDB 复制到它自己的 HTTP 响应中返回给客户端(或在出现错误时显示一条消息)。然后在需要数据库访问的后续请求中,它将 cookie(现在来自客户端请求)再次转发回数据库。
传统方法,您只需使用 CouchDB 作为数据存储并维护自己的 "user" entries/indexes。确保您使用密码 storage/handling 的当前最佳做法或使用为您处理这些细节的库。中间件作为 "itself" 连接到数据库,并根据自己的 session 处理使用自己的逻辑处理 read/write 权限。
或者 — 某种混合方法 — 您可以使用“/_session”API only 来查看是否CouchDB 接受用户名+密码为有效。如果是,请为该用户创建一个单独的 middleware-handled session。 (基本上你只使用 CouchDB 的 _user 数据库作为 "password handling library",其余的是传统方法,其中访问控制全部在中间件而不是在数据库中实现。)
对于 real-world 生产资料,我倾向于只使用后两种(或 one-and-a-half 考虑到较早的编号……)——第一种方法很有趣,但 CouchDB 缺乏document-level 读取权限通常意味着授予用户 nearly-direct 访问数据库服务器的权限在实践中是站不住脚的。
更新:你的问题现在清楚地表明你希望客户端应用程序直接与两个服务器对话:应用程序(以前"middleware") 服务器 和 CouchDB(数据库)服务器。我保留上面的内容,因为我认为它仍然有些用处,并为这次更新提供了一点background/context。
您怀疑 Proxy Authentication 是错误的解决方案是正确的:它 不是 用于 end-user 用法,但实际上是为了替换 cookie-forwarding "trick" 以上#1 的部分。也就是说,代理身份验证是当您完全信任一个方(即您的中间软件)在其工作时提供用户信息代表 个用户。但是您希望用户 直接 与数据库对话,并且您不能通过 X-Auth-CouchDB-Token
.
我会听从您对 OAuth 选项的判断。我确实认为它更接近你想要的,但很明显,你正在以某种方式对不同服务的用户进行身份验证,并且不需要存储 per-user keys in CouchDB 本身。 OAuth 1.0 要求的请求签名意味着您也需要客户端应用程序的 HTTP 库的支持。
我看到了一些选项,无需构建自定义 CouchDB 插件,它们可以让您的 app 服务器向经过身份验证的用户分发令牌,您的 database 服务器将接受:
毕竟代理!也就是说,将您的数据库服务器隐藏在您的应用程序服务器或另一个轻量级自定义 reverse-proxy 后面。所有这些中间件需要做的就是检查您现有的客户端应用程序 session(cookie 或其他身份验证 header),如果它有效,则设置 CouchDB 将接受的内部 proxy auth headers——然后它转发其余 request/response 逐字记录。
确定性密码,per-user如果它让你感觉更好。使用只有它知道的秘密配置您的应用服务器,然后将每个用户密码设置为
HMAC(username, app_server_secret)
之类的内容。现在,当您想为用户生成令牌时,您的应用服务器可以在 per-user 的基础上生成密码。请注意,这并不比仅使用app_server_secret
作为每个用户的密码更安全——CouchDB 已经独立地对每个用户密码进行加盐和哈希处理,因此如果有人获得了数据库而不是您应用程序的配置值攻击者无法区分两者。在这两种情况下,防止未经授权的数据库使用完全取决于app_server_secret
保密。Re-implement CouchDB 当前的 cookie 生成算法。 CouchDB 的 cookie 算法(view source) is basically
data = username + ':' + timestamp; base64(data + ':' + sha_mac(data, secret))
. Wheresecret
is thecouch_httpd_auth.secret
value plus the user'ssalt
value. You can tell your app server the couchdb_httpd_auth/secret 值,它可以按照相同的步骤生成您提供给客户端应用程序的有效 cookie,CouchDB 将接受它作为自己的。此 cookie 将在时间戳 + 配置之前有效couch_httpd_auth/timeout。看起来 "hacky",这可能是最接近您要求的,尽管您仍然需要以某种方式 set/disable 用户的实际密码。
在
这是我的 python3 生成 cookie 的实现(使用 pycouchdb 与沙发交互):
def generate_couchdb_cookie(couchAddress, couchSecret, username):
timestamp = format(int(time.time()), 'X')
data = username + ":" + timestamp
server = pycouchdb.Server(couchAddress)
db = server.database("_users")
doc = db.get("org.couchdb.user:" + username)
salt = doc["salt"]
secret = couchSecret + salt
hashed = hmac.new(secret.encode(), data.encode(), hashlib.sha1).digest()
inbytes = data.encode() + ":".encode() + hashed
result = base64.urlsafe_b64encode(inbytes)
return "AuthSession=" + (result.decode("utf-8")).rstrip('=')