【问题标题】:By what process does HTTP Basic Auth actually authenticate credentials?HTTP Basic Auth 通过什么过程实际验证凭据?
【发布时间】:2021-11-30 03:26:05
【问题描述】:

我一直在 HTTP Basic Authentication 上让自己跟上进度。

我了解这从根本上说是一种不安全的访问机制(即使通过 HTTPS 使用,也应该如此),但我认识到 HTTP Basic Auth 并非完全没有实用性,我想熟悉它,即使很少出现我可能会部署它的情况。


到目前为止我的理解:

经过一番阅读,我明白了:

  • 服务器可以通过返回401 (unauthorised)请求资源授权
  • WWW-Authenticate 响应标头确定用于访问此资源的身份验证将是Basic
  • HTTP Basic Authentication 要求:
  • a) 用户通过浏览器生成的控制台提交用户名和密码;或者那个
  • b) 手动成功提交后,相同的用户名和密码组合将通过 Authorization 请求标头自动提交到每个 HTTP 请求

到目前为止,一切都很好。


需要注意的问题:

我还了解到 HTTP Basic Auth 存在一些问题,这些问题随着时间的推移而演变,例如:

  • 部分浏览器不再接受https://mylogin:mypassword@example.com/my-resource.html等URL语法
  • 如果 PHP 正在通过 CGIFastCGI 运行,那么提交的 授权凭据 将不会传递给 $_SERVER['HTTP_AUTHORIZATION'],除非是 hack已部署 - 最常见的建议是通过 .htaccess mod_rewrite 重写 URL

以及从一开始就一直存在的其他问题,例如:

  • “注销”HTTP Basic Auth 是一个非常重要的问题,因为它从未打算或设计为具有注销机制

拼图缺失的部分:

但是,我仍然感到困惑,因为即使用户(或Authorization 请求标头)提交了有效的身份验证凭据......服务器如何知道它们是有效的?

在我遇到的每个讨论 HTTP 基本身份验证 机制的文档中,讨论都没有到达凭据实际经过身份验证的那一点。


问题:

提交的凭据实际上是如何经过身份验证的?

服务器将提交的凭据与... 任何东西进行比较


额外问题

NB 这与我上面的主要问题有关,因为我使用 .htaccessqueryString 参数来传递凭据(见下文)呈现了 HTTP Basic 的部署Auth 完全多余 - 如果我沿着这条路线走,我可以单独使用 .htaccessqueryString 参数传递凭据,我根本不需要部署 HTTP Basic Auth

作为一种规避 CGI/FastCGI 问题的方法,我经常看到引用这些 .htaccess 行的变体:

RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]

RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization},last]

SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0 

尽管我尝试使用这些方法中的任何一种来使用凭据填充 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 传输给它们的值?)

【问题讨论】:

  • “将凭据附加为 queryString 参数” - 尽管第一个 条件 似乎缺少某些内容 - 写它永远不会匹配? “我根本无法让环境变量工作” - 大概您尝试过像 SetEnvIf ^ ^ TEST=1RewriteRule ^ - [E=TEST:1] 这样的简单测试?这仍然没有设置环境变量吗? (你如何检查环境变量?使用 phpinfo()?).htaccess 中还有其他指令吗?重写引擎的任何循环都会导致 env var 在每个附加循环上使用 REDIRECT_ 前缀重命名。
  • 您是否专门询问有关 PHP 的 HTTP 基本身份验证?
  • @MrWhite - 经过反思,我想我可能是的。但我现在只是在下面讨论了在非 LAMP 堆栈中使用 HTTP Basic Auth 之后才得出结论。 (注意我没有 Node.js 背景。我今年才开始从 LAMP 分支到 Deno + koa,除此之外,自 2009 年以来我只知道 LAMP。之前2009 年,我从来没有接触过服务器端。)
  • 好的,但是使用 Apache 作为身份验证器是 HTTP Basic Auth IMO 的明显选择。 服务器 然后确实“知道”凭据是否有效。无需为传递 Authorization 标头而烦恼。尽管我仍然很好奇为什么您似乎没有看到 _any_(?) 环境变量被传递给 PHP?您是否尝试了一个简单的测试并检查了$_SERVER 超全局?

标签: php .htaccess http-headers basic-authentication http-authentication


【解决方案1】:

解释中没有实际的身份验证,因为您如何执行此操作完全取决于您。

用户名和密码以明文形式*发送给您。因此,就像有人提交带有 usernamepassword 字段的登录表单一样,您可以决定如何处理它。

*:是的,它们是 base64 编码的。但我的意思是,它们没有经过散列或加密或类似的东西。

所以,你可以...

  • 将用户名和密码与其他明文值进行比较,例如来自环境变量的值
  • 散列密码并将散列值与某些内容进行比较,例如数据库中的散列密码
  • 将凭据转发到某些外部身份验证服务
  • ...?

在服务器上的外观示例(此示例假设使用 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 中概述的其他问题,最重要的是,现在明文密码验证应该有意设计为一种缓慢的操作以避免暴力攻击,但是对于像基本身份验证这样的系统,必须在其中执行此操作每个请求,这都会给服务器带来沉重的负担,很容易被滥用于拒绝服务攻击。


关于如何将授权标头转发到 PHP:除非以奇怪的方式配置其他内容,否则只需在 Apache 的配置中设置 CGIPassAuth on

来自docs

CGIPassAuth 允许脚本访问 HTTP 授权标头,例如 Authorization,这是实现 HTTP 基本身份验证的脚本所必需的。通常这些 HTTP 标头对脚本是隐藏的。这是为了在 Web 服务器中启用 HTTP Basic 身份验证时禁止脚本查看用于访问服务器的用户 ID 和密码。当允许脚本实现 HTTP Basic 身份验证时,应使用该指令。

之后,$_SERVER['HTTP_AUTHORIZATION']变量应该设置好,然后PHP也可以parse the header for you automatically并提供$_SERVER['PHP_AUTH_USER']$_SERVER['PHP_AUTH_PW']

【讨论】:

  • 回复:“解释中没有实际的身份验证,因为你如何做到这一点完全取决于你。”啊哈。谢谢,@CherryDT。我需要读那个。这我所怀疑的,但我读过的少数几篇文章都没有明确说明这一点。在这一点上,我上面的额外问题变得更加重要,因为如果我不能在没有首先使用.htaccess 将凭据转换为queryString 参数的情况下访问 PHP 中的凭据......好吧,我可以简单地设置 PHP-使用参数的基于身份验证...我根本不需要 HTTP Basic Auth
  • 不过,这是 Apache、FastCGI 和 PHP 的问题 - 与 HTTP 基本身份验证本身无关。当我将 Caddy 与 PHP 或完全不同的东西(例如 node.js 或 ASP.NET)一起使用时,我没有这个问题。使用 HTTP 基本身份验证的好处是可以轻松地将 basic 身份验证机制添加到页面或端点。您不需要登录表单,因为浏览器会提供它,您不需要保持登录状态,因为浏览器会记住后续请求的凭据等 - 您只需要验证这些凭据即可。
  • 在其最基本的形式中,您只需要在主要处理完成之前写上 if (authorization header != expected value) then { return 401 response with www-authenticate header; exit } 的内容即可,您的页面或任何内容现在都受到保护,以防止未经授权的使用,以某种方式对用户来说仍然很方便(他们只需要在出现提示时输入他们的凭据)。
  • 对。但是我不能在 PHP 中访问 Authorization header 而不在 .htaccess 中使用 mod_rewrite 指令绕过房屋(即使这样我也无法让环境变量工作),所以,正如你所说,它是一个 “Apache、FastCGI 和 PHP” 问题。也许我花了一周时间反复回到这个问题并最终未能完全理解它的答案(尽管如此,学习了很多关于 HTTP 标头、ENV 变量等)的答案很简单:“无论如何由于历史上的兼容性,现在存在的 LAMP 堆栈在很大程度上与 HTTP 基本身份验证不兼容。”
  • 嗯,我不知道为什么它会成为一个大问题。听起来好像缺少一些基本的东西。据我所知,Authorization 标头由于“安全原因”没有被转发(假设 Apache 将处理身份验证并且 PHP 脚本不应该看到凭据),但这应该是只需在 Apache 的配置或 .htaccess 文件中设置 CGIPassAuth On 即可更改。请参阅httpd.apache.org/docs/2.4/en/mod/core.html#cgipassauth - 这不仅应该使HTTP_AUTHORIZATION 工作,而且还可以使其他相关变量,例如PHP_AUTH_USERPHP_AUTH_PW
【解决方案2】:

所以这只是我的理解,但我对此非常有信心......: 如果您要发送 HTTP 基本身份验证,则您提供的用户名和密码会自动编码为 base64 并在标头中发送,例如:Basic ZG9kb3BhbmE6YXV0bw== ,当在另一端的标头中收到时,它在标头授权值中它仍然看起来像:Basic YmFzaWM6YXV0bw== :) => 一个快速的仅 base64 解码将从这里返回: dodopana:auto - 这是我给帖子的用户名:密码。

到目前为止这么好?! :) 现在,基本身份验证已发送和接收。 下一步 - 不是服务器步骤......它是 API 或端点软件本身,因为它需要令牌或用户名和密码......应该验证 API 供应商凭据前面的解密身份验证。

它只是通过一个加密的字符串,并且所述标头的比较和/或验证现在在 API 软件手中,或者我应该说 - 程序员应该验证它...

【讨论】:

  • base64 不是一种加密方法,它是一种编码。非常重要的区别!编码在安全性方面的价值为零。
  • 谢谢你,@Shlomtzion。您写道:“API 或端点软件 [...] 应验证 [...] 身份验证”。这是我不明白的部分。 API 从哪里获取凭据?我所见过的讨论 HTTP Basic Auth 的文章似乎都没有对此进行扩展。您能否扩展您的答案以举例说明?非常感谢。
  • 例如,假设您的服务器上有一个安全连接和一个令牌表(您是这里的 API 所有者/创建者),现在,您知道哪些用户允许使用哪些密码。 ..当一个新的 API 用户与您联系时,您向他提供用户和密码,当这些在他的 API 请求中传递时,您可以在您的平台上验证它们,但是您设计它......我的意思是......当你提供一个像 API 这样的服务,你也为它创建一个安全主干......
  • 抱歉,我没有说得那么清楚。让我换个说法:API 是否从 Authorization 标头中获取提交的凭据?如果是这样,怎么做?如果没有,它从哪里获得提交的凭据?
  • 是的,确切地说,与呼叫一起到达的 Auth 标头(并由呼叫者填写)具有作为基本身份验证标头中的字符串提供给他的凭据......但是在不同的身份验证中方法可能有不同的方式来传输凭据。
猜你喜欢
  • 1970-01-01
  • 2018-08-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-11
  • 2021-06-04
  • 1970-01-01
  • 2016-10-30
相关资源
最近更新 更多