HTTP Basic Auth 通过什么过程实际验证凭据?
By what process does HTTP Basic Auth actually authenticate credentials?
我一直在加快速度 HTTP Basic Authentication。
我知道这从根本上说是一种不安全的访问机制(即使通过 HTTPS 使用,它始终应该是)但我认识到 HTTP Basic Auth 并非完全没有实用性我想熟悉它,即使很少出现我可能会部署它的情况。
目前我的理解:
经过一番阅读,我明白了:
- 服务器可以通过返回
401 (unauthorised)
请求资源授权
- a
WWW-Authenticate
响应 header 确定用于访问此资源的身份验证将是 Basic
HTTP Basic Authentication
要求:
- a) 用户通过 browser-generated 控制台提交用户名和密码;或者那个
- b) 手动成功提交后,相同的用户名和密码组合将通过每个 HTTP 请求前面的
Authorization
请求 header 自动提交
到目前为止,还不错。
需要注意的问题:
我也了解到 HTTP Basic Auth 存在一些随着时间的推移而演变的问题,例如:
- 某些浏览器不再接受 URL 语法,例如
https://mylogin:mypassword@example.com/my-resource.html
- 其中 PHP 通过 CGI 或 FastCGI 运行 然后提交 授权凭据 不会传递给
$_SERVER['HTTP_AUTHORIZATION']
除非部署黑客攻击 - 最常见的建议是 URL 通过 .htaccess mod_rewrite
重写
以及其他从一开始就一直存在的问题,例如:
- “注销”HTTP Basic Auth 是一个 non-trivial 问题,因为它从未打算或设计为具有 log-out 机制
缺少的一块拼图:
但是,我仍然很困惑,因为即使用户(或 Authorization
请求 header)提交了有效的身份验证凭据...服务器如何 知道它们是有效的吗?
在我遇到的每份文档中都讨论了 HTTP 基本身份验证 的机制,讨论在实际验证凭据时就停止了。
问题:
提交的凭证实际上是如何验证的?
服务器在哪里比较提交的凭据... 任何东西?
奖金问题:
N.B. 这与我上面的主要问题有关,因为我使用了 .htaccess
和 queryString
传递凭据的参数(见下文)使 HTTP Basic Auth 的部署完全多余 - 如果我走这条路,我可以使用 .htaccess
和 queryString
传递凭据参数,我根本不需要部署 HTTP Basic Auth。
作为绕过 CGI/FastCGI 问题的一种方法,我经常看到引用这些 .htaccess
行的变体:
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
或
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization},last]
或
SetEnvIf Authorization .+ HTTP_AUTHORIZATION=[=12=]
尽管我尝试使用这些方法中的任何 来填充PHP 具有凭据的环境变量都被证明是不成功的。
相反,我成功部署了以下内容(使用 queryString
参数而不是环境变量):
# WRITE HTTP BASIC AUTHENTICATION CREDENTIALS TO QUERY STRING
RewriteCond %{HTTP:Authorization} [NC]
RewriteCond %{QUERY_STRING} ^basicauth=login$ [NC]
RewriteRule ^my-document.php https://example.com/my-document.php?basicauth=login-submitted&credentials=%{HTTP:Authorization} [NC,L]
将凭据附加为 queryString
参数。
我对上面的 mod_rewrite
解决方案并不满意,但我很困惑,我根本无法让环境变量工作。
我很想知道在谈到后者时是否有明显的遗漏 - 比如......它们是否可以在我的 PHP 配置中关闭?
(如果是这样,我需要检查哪些条目 PHPInfo
以确认它们确实已打开并接受通过 mod_rewrite
传输给它们的值?)
该解释未涉及实际身份验证,因为您如何执行此操作完全取决于您。
用户名和密码以明文形式*发送给您。因此,就像有人提交带有 username
和 password
字段的登录表单一样,您可以决定如何处理它。
*:是的,它们是 base64 编码的。但我的意思是它们没有经过哈希处理或加密或类似的东西。
所以,你可以...
- 将用户名和密码与其他 clear-text 值进行比较,例如来自环境变量
- 散列密码并将散列与某些东西进行比较,例如数据库中的散列密码
- 将凭据转发给某些外部身份验证服务
- ...?
在服务器上看起来如何的示例(此示例假设使用 node.js、Koa、koa-router、Mongoose 和 bcrypt,并且为了简化目的,它假设用户名和密码都不是允许包含冒号):
router.get('/protectedPage', async ctx => {
const [authMethod, authData] = ctx.get('authorization')?.split(' ') ?? []
if (authMethod === 'Basic') {
try {
const decoded = Buffer.from(authData, 'base64').toString()
const [username, password] = decoded.split(':')
// Find user in database and verify password
const user = await User.findOne({ username })
if (user && await bcrypt.compare(password, user.encryptedPassword)) {
// User is authenticated now
ctx.state.user = user
}
} catch (e) {
console.error(`Failed to process auth header "${authData}"`, e)
}
}
if (ctx.state.user) {
return ctx.render('protectedPage')
} else {
return ctx.throw(401, null, {
headers: { 'WWW-Authenticate': 'Basic realm="Protected Page"' }
})
}
})
本质上,您如何使用凭据不在该机制的范围内。
(关于注销:我通常将人们重定向到 https://_logout_@example.com
或类似的东西,这样从现在开始请求将使用这个无效的用户名和空密码,再次导致 401。请注意,这赢了如果页面在缓存中则不会工作,因为无论如何都会传送缓存版本,因此在这种情况下可能需要额外考虑这一方面 - 可能无论如何都不应该缓存经过身份验证的页面。)
关于“不安全”方面:如果您使用 HTTPS,这并不是真正的不安全,因为“凭据在每个请求中以明文形式发送”方面不再相关。然而,它确实存在 this answer 中概述的其他问题,最重要的是,现在 clear-text 密码验证应该有意设计为一个缓慢的操作以避免 brute-force 攻击,但是对于像 basic auth 这样的系统,这必须对每个请求执行,这给服务器带来了沉重的负担,很容易被滥用 denial-of-service 攻击。
关于如何获得授权 header 转发给 PHP: 除非以奇怪的方式配置了其他东西,否则在 Apache 的配置中设置 CGIPassAuth on
应该是所有需要的。
来自docs:
CGIPassAuth
allows scripts access to HTTP authorization headers such as Authorization
, which is required for scripts that implement HTTP Basic authentication. Normally these HTTP headers are hidden from scripts. This is to disallow scripts from seeing user ids and passwords used to access the server when HTTP Basic authentication is enabled in the web server. This directive should be used when scripts are allowed to implement HTTP Basic authentication.
之后要适当设置$_SERVER['HTTP_AUTHORIZATION']
变量,然后PHP也可以parse the header for you automatically提供$_SERVER['PHP_AUTH_USER']
和$_SERVER['PHP_AUTH_PW']
.
所以这只是我的理解,但我对此很有信心......:
如果您要发送 HTTP 基本身份验证,您将提供一个用户名和密码,该用户名和密码会自动编码为 base64 并在 header 中发送,例如:Basic ZG9kb3BhbmE6YXV0bw== ,当在 header 中收到时另一边在 headers 授权值中,它仍然看起来像:Basic YmFzaWM6YXV0bw== :) => 一个快速的 base64 解码将 return 从这个:dodopana:auto - 这是username:password我给了post.
到目前为止还好吗?! :) 现在,基本身份验证已经发送和接收。
下一步 - 不是服务器步骤...它是 API 或端点软件本身,因为它需要令牌或用户名和密码...应该验证 API 前面的解密身份验证供应商证书。
它只是传递一个加密的字符串,并且 header 的比较和/或验证现在在 API 软件手中,或者我应该说 - 程序员应该验证它...
我一直在加快速度 HTTP Basic Authentication。
我知道这从根本上说是一种不安全的访问机制(即使通过 HTTPS 使用,它始终应该是)但我认识到 HTTP Basic Auth 并非完全没有实用性我想熟悉它,即使很少出现我可能会部署它的情况。
目前我的理解:
经过一番阅读,我明白了:
- 服务器可以通过返回
401 (unauthorised)
请求资源授权
- a
WWW-Authenticate
响应 header 确定用于访问此资源的身份验证将是Basic
HTTP Basic Authentication
要求:- a) 用户通过 browser-generated 控制台提交用户名和密码;或者那个
- b) 手动成功提交后,相同的用户名和密码组合将通过每个 HTTP 请求前面的
Authorization
请求 header 自动提交
到目前为止,还不错。
需要注意的问题:
我也了解到 HTTP Basic Auth 存在一些随着时间的推移而演变的问题,例如:
- 某些浏览器不再接受 URL 语法,例如
https://mylogin:mypassword@example.com/my-resource.html
- 其中 PHP 通过 CGI 或 FastCGI 运行 然后提交 授权凭据 不会传递给
$_SERVER['HTTP_AUTHORIZATION']
除非部署黑客攻击 - 最常见的建议是 URL 通过.htaccess mod_rewrite
重写
以及其他从一开始就一直存在的问题,例如:
- “注销”HTTP Basic Auth 是一个 non-trivial 问题,因为它从未打算或设计为具有 log-out 机制
缺少的一块拼图:
但是,我仍然很困惑,因为即使用户(或 Authorization
请求 header)提交了有效的身份验证凭据...服务器如何 知道它们是有效的吗?
在我遇到的每份文档中都讨论了 HTTP 基本身份验证 的机制,讨论在实际验证凭据时就停止了。
问题:
提交的凭证实际上是如何验证的?
服务器在哪里比较提交的凭据... 任何东西?
奖金问题:
N.B. 这与我上面的主要问题有关,因为我使用了 .htaccess
和 queryString
传递凭据的参数(见下文)使 HTTP Basic Auth 的部署完全多余 - 如果我走这条路,我可以使用 .htaccess
和 queryString
传递凭据参数,我根本不需要部署 HTTP Basic Auth。
作为绕过 CGI/FastCGI 问题的一种方法,我经常看到引用这些 .htaccess
行的变体:
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
或
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization},last]
或
SetEnvIf Authorization .+ HTTP_AUTHORIZATION=[=12=]
尽管我尝试使用这些方法中的任何 来填充PHP 具有凭据的环境变量都被证明是不成功的。
相反,我成功部署了以下内容(使用 queryString
参数而不是环境变量):
# WRITE HTTP BASIC AUTHENTICATION CREDENTIALS TO QUERY STRING
RewriteCond %{HTTP:Authorization} [NC]
RewriteCond %{QUERY_STRING} ^basicauth=login$ [NC]
RewriteRule ^my-document.php https://example.com/my-document.php?basicauth=login-submitted&credentials=%{HTTP:Authorization} [NC,L]
将凭据附加为 queryString
参数。
我对上面的 mod_rewrite
解决方案并不满意,但我很困惑,我根本无法让环境变量工作。
我很想知道在谈到后者时是否有明显的遗漏 - 比如......它们是否可以在我的 PHP 配置中关闭?
(如果是这样,我需要检查哪些条目 PHPInfo
以确认它们确实已打开并接受通过 mod_rewrite
传输给它们的值?)
该解释未涉及实际身份验证,因为您如何执行此操作完全取决于您。
用户名和密码以明文形式*发送给您。因此,就像有人提交带有 username
和 password
字段的登录表单一样,您可以决定如何处理它。
*:是的,它们是 base64 编码的。但我的意思是它们没有经过哈希处理或加密或类似的东西。
所以,你可以...
- 将用户名和密码与其他 clear-text 值进行比较,例如来自环境变量
- 散列密码并将散列与某些东西进行比较,例如数据库中的散列密码
- 将凭据转发给某些外部身份验证服务
- ...?
在服务器上看起来如何的示例(此示例假设使用 node.js、Koa、koa-router、Mongoose 和 bcrypt,并且为了简化目的,它假设用户名和密码都不是允许包含冒号):
router.get('/protectedPage', async ctx => {
const [authMethod, authData] = ctx.get('authorization')?.split(' ') ?? []
if (authMethod === 'Basic') {
try {
const decoded = Buffer.from(authData, 'base64').toString()
const [username, password] = decoded.split(':')
// Find user in database and verify password
const user = await User.findOne({ username })
if (user && await bcrypt.compare(password, user.encryptedPassword)) {
// User is authenticated now
ctx.state.user = user
}
} catch (e) {
console.error(`Failed to process auth header "${authData}"`, e)
}
}
if (ctx.state.user) {
return ctx.render('protectedPage')
} else {
return ctx.throw(401, null, {
headers: { 'WWW-Authenticate': 'Basic realm="Protected Page"' }
})
}
})
本质上,您如何使用凭据不在该机制的范围内。
(关于注销:我通常将人们重定向到 https://_logout_@example.com
或类似的东西,这样从现在开始请求将使用这个无效的用户名和空密码,再次导致 401。请注意,这赢了如果页面在缓存中则不会工作,因为无论如何都会传送缓存版本,因此在这种情况下可能需要额外考虑这一方面 - 可能无论如何都不应该缓存经过身份验证的页面。)
关于“不安全”方面:如果您使用 HTTPS,这并不是真正的不安全,因为“凭据在每个请求中以明文形式发送”方面不再相关。然而,它确实存在 this answer 中概述的其他问题,最重要的是,现在 clear-text 密码验证应该有意设计为一个缓慢的操作以避免 brute-force 攻击,但是对于像 basic auth 这样的系统,这必须对每个请求执行,这给服务器带来了沉重的负担,很容易被滥用 denial-of-service 攻击。
关于如何获得授权 header 转发给 PHP: 除非以奇怪的方式配置了其他东西,否则在 Apache 的配置中设置 CGIPassAuth on
应该是所有需要的。
来自docs:
CGIPassAuth
allows scripts access to HTTP authorization headers such asAuthorization
, which is required for scripts that implement HTTP Basic authentication. Normally these HTTP headers are hidden from scripts. This is to disallow scripts from seeing user ids and passwords used to access the server when HTTP Basic authentication is enabled in the web server. This directive should be used when scripts are allowed to implement HTTP Basic authentication.
之后要适当设置$_SERVER['HTTP_AUTHORIZATION']
变量,然后PHP也可以parse the header for you automatically提供$_SERVER['PHP_AUTH_USER']
和$_SERVER['PHP_AUTH_PW']
.
所以这只是我的理解,但我对此很有信心......: 如果您要发送 HTTP 基本身份验证,您将提供一个用户名和密码,该用户名和密码会自动编码为 base64 并在 header 中发送,例如:Basic ZG9kb3BhbmE6YXV0bw== ,当在 header 中收到时另一边在 headers 授权值中,它仍然看起来像:Basic YmFzaWM6YXV0bw== :) => 一个快速的 base64 解码将 return 从这个:dodopana:auto - 这是username:password我给了post.
到目前为止还好吗?! :) 现在,基本身份验证已经发送和接收。 下一步 - 不是服务器步骤...它是 API 或端点软件本身,因为它需要令牌或用户名和密码...应该验证 API 前面的解密身份验证供应商证书。
它只是传递一个加密的字符串,并且 header 的比较和/或验证现在在 API 软件手中,或者我应该说 - 程序员应该验证它...