使用 .htaccess 将 http 重定向到 https 时,某些 url 出现奇怪的 401 错误

Posted

技术标签:

【中文标题】使用 .htaccess 将 http 重定向到 https 时,某些 url 出现奇怪的 401 错误【英文标题】:strange 401 error appears for some urls when using .htaccess to redirect http to https 【发布时间】:2012-03-03 14:04:36 【问题描述】:

好的,这是第 7 天没有成功尝试找到为什么出现 401 错误的答案...

现在, 根文件夹中的 .htaccess 仅包含 3 个字符串(已简化),并且项目中没有更多 .htaccess 文件:

RewriteEngine On
RewriteCond %HTTPS !on
RewriteRule (.*) https://%HTTP_HOST%REQUEST_URI

因此,它将所有请求重定向为 https。它适用于任何 url,甚至适用于 /administration 目录。

所以,

http://mydomain.com

变成

https://mydomain.com

如果输入了https://mydomain.com,则没有重定向。

http://mydomain.com/administration/index.php

变成

https://mydomain.com/administration/index.php

如果输入了https://mydomain.com/administration/index.php,则没有重定向。

很清楚,问题在下面。

我希望 /administration 目录受密码保护。我的共享主机控制面板允许在不手动创建 .htaccess 和 .htpasswd 的情况下保护目录(您选择要保护的目录,创建用户名和密码,然后自动创建 .htaccess 和 .htpasswd)。因此,.htaccess 出现在 /administration 文件夹中。 .htpasswd 出现在其他地方,.htpasswd 的路径是正确的,并且一切看起来都正确(它的工作方式与手动创建它的方式相同)。所以,项目中有2个.htaccess文件,一个在根目录,一个在/administration目录(目录下有.htpasswd,.htaccess知道在哪里)。

创建密码后, 结果是:

你输入:

https://mydomain.com/administration/index.php

然后它要求输入密码。 如果输入正确, 显示https://mydomain.com/administration/index.php结果:完美运行。

但是,如果你输入 http://mydomain.com/administration/index.php(是的,http,没有 S) 然后而不是重定向到相同但 https 页面, 它重定向到

https://mydomain.com/401.shtml (starts with httpS)

由于未知原因,甚至不询问密码。 为什么?

我已经就这个问题联系了客户支持,他们确定问题出在 .htaccess 文件中,并且他们没有修复 .htaccess 文件(很明显,他们没有,我不介意)。

为什么会这样? 我是否忘记在 .htaccess 文件中添加一些标志或一些更改默认设置的选项?

P.S.为文件夹 /administration 手动创建 .htaccess 和 .htpasswd(不是从主机控制面板)会导致相同的 401 错误,如果不是 https,但输入了 http。

问题仅出现在 /administration 目录的 URL 上。

谢谢。

【问题讨论】:

我假设您使用的是 Apache。你真的收到错误吗?我问是因为 401 是 Apache 在需要身份验证时发送的响应。事实上,据我了解,浏览器应该在收到 401 标头后立即提示输入用户名和密码。在不同协议下重定向到同一 URL 时,可能出现问题,导致浏览器不提示输入凭据。查看此处以供参考:httpd.apache.org/docs/1.3/howto/auth.html 在“基本身份验证的工作原理”下。你用的是什么浏览器? 是的,它在 Apache 下。它至少发生在 Firefox、最流行的 Chrome 和 IE6.0(我知道它已经很老了)。我测试的所有浏览器都会出现相同的错误。 是的,这个问题确实超出了我的专业范围。我希望我能帮助你指出正确的方向。祝你好运! 【参考方案1】:

尝试改用这个。不是 L 和 R 标志。

RewriteEngine On
RewriteCond %HTTPS !on
RewriteRule (.*) https://%HTTP_HOST%REQUEST_URI [L,R=301]

还要先清除浏览器缓存,以删除旧的错误重定向。

如果这不起作用,请尝试使用它。

RewriteCond %HTTPS !on
RewriteCond %THE_REQUEST ^(GET|HEAD)\ ([^\ ]+)
RewriteRule ^ https://%HTTP_HOST%2 [L,R=301]

我觉得写它有点糟糕,因为在我看来这有点骇人听闻。

编辑 似乎第二个选项解决了这个问题。所以这里是关于它为什么起作用的解释。

认证模块在重写模块之前执行。由于首次请求页面时未发送用户名和密码,因此身份验证模块在内部将请求 url '重写'为 401 页面的 url。在这个 mod_rewrite 出现之后,%THE_REQUEST 现在包含 401.shtml 而不是原来的 url。所以生成的重定向包含 401.shtml,而不是你想要的 url。

获取到原始(不是“重写”)的 url,您需要从 %THE_REQUEST 中提取它。 THE_REQUEST 的格式为[requestmethod] [url] HTTP[versionnumber]。 RewriteCond 仅提取中间部分 ([url])。

为了完整起见,我在第二个解决方案中添加了 [L,R=301] 标志。

【讨论】:

第一个具有相同的行为 - 输入 http 时会导致 401 错误(对于 https,这是可以的)。第二个导致“内部服务器错误服务器遇到内部错误或配置错误,无法完成您的请求”。任何网址。 我忘记了第二个空格字符的转义。 500 错误现在消失了。编辑后的代码见上文。 有效!!!!!!非常感谢!适用于所有浏览器。原因是什么,你怎么知道这应该有效? (你能解释一下第二个字符串/#2和#1之间的区别吗?当然如果可以的话......)非常感谢你的帮助,你是我的救星。顺便说一句,最后我需要 last_line [L] 标志吗?我可能会添加www。并稍微修改 URL 的视图。非常感谢!!!谢谢谢谢谢谢!!! 不客气。很高兴有帮助。有关说明,请参见上面的编辑。记得接受答案;谢谢。【参考方案2】:

我想我找到了更好的解决方案!

只需将其添加到您的 .htaccess 中

ErrorDocument 401 "Unauthorized"

解决方案位于:

http://forum.kohanaframework.org/discussion/8934/solved-for-reall-this-time-p-htaccess-folder-password-protection/

-- 编辑

我最终发现问题的根本原因是 ModSecurity 标记了我的 POST 数据(脚本和 iframe 标签会导致问题)。它会尝试返回 401/403,但找不到默认错误文档,因为 ModSecurity 使我的 htaccess 失控。

使用 ErrorDocument 401 "Unauthorized" 绕过了丢失错误文档的问题,但没有解决根本原因。

为此,我最终使用 javascript 将“盐”添加到既不是空格也不是单词字符的任何内容中......

  $("form").submit(function(event) 
    $("textarea,[type=text]").each(function() 
      $(this).val($(this).val().replace(/([^\s\w])/g, "foobar$1salt"));
    );
  );

然后 PHP 再次剥离盐...

function stripSalt($value) 
  if (is_array($value)) $value = array_map('stripSalt', $value);
  else $value = preg_replace("/(?:foobar)+(.)(?:salt)+/", "$1", $value);

  return $value;

$_POST = stripSalt($_POST);

非常、非常、非常重要的注意事项: 不要使用“foobar$1salt”,否则这篇文章只是向黑客展示了如何绕过你的 ModSecurity!

正则表达式注释: 我认为可能值得一提的是这里发生了什么......

(?:foobar)+ = 匹配前半部分 salt 一次或多次,但不要将其存储为匹配组;

(.) = 匹配任何字符并将其存储为第一个也是唯一的组(可通过 $1 访问);

(?:salt)+ = 匹配后半部分 salt 一次或多次,但不要将其存储为匹配组。

每个字符多次匹配盐很重要,因为如果您点击提交然后使用返回按钮,您将返回到所有盐仍然存在的表单。再次点击提交,添加更多的盐。这可能会一次又一次地发生,直到您最终得到以下结果: foob​​arfoobarfoobarfoobar>saltsaltsaltsalt

【讨论】:

我遇到了同样的问题。不知何故,上面接受的“答案”对我不起作用。但是这个做到了。 虽然这个解决方案对我有用,但我也从裸域重定向到 www。出于某种原因,此解决方案将要求我输入我的凭据两次。 Gerben 的回答,无论出于何种原因,都不需要双重凭据输入。 或许在这种情况下,最好确保您将数据提交到已经包含 www 的 url?【参考方案3】:

我对上面的解决方案不满意,所以我想出了另一个解决方案:

在现代 Web 服务器配置中,我们应该将所有流量重定向到 HTTPS,这样用户就无法在没有 HTTPS 的情况下访问任何内容。在用户使用 HTTPS 浏览我们的内容后,我们可以使用身份验证。考虑到这一点,我们可以将身份验证指令包装在 If 指令中:

<If "%HTTPS == 'on'">
  AuthType Basic
  ...
</If>

您可以随意保留和使用 Rewrite 指令。

有了这个解决方案:

不得按照 Hoogs 的建议更改 ErrorDocument 您不得按照 Gerben 的建议以骇人听闻的方式从 THE_REQUEST 中提取路径

【讨论】:

【参考方案4】:

这种类型的事情是在没有您面前的框的情况下在 Apache 上进行故障排除有点棘手,但我认为正在发生的事情是正在处理您的重写指令在路径解析之后,就是路径解析有密码要求。

稍微备份一下,在 Apache 中解析 URL 的方式是请求进入并从模块传递到模块,有点像桶旅。每个模块做自己的事情....一些模块进行内容协商,一些将 URL 转换为文件路径,一些检查身份验证,其中之一是 mod_rewrite ...

您在配置中看到这一点的一个地方实际上是有一个 Location 指令和一个 Directory 指令,它们在大多数方面看起来都是一样的,但它们是不同的,因为 Locations 谈论 URL,Directories 谈论文件系统路径。

无论如何,我的猜测是,Apache 发现它需要密码才能访问该内容,然后才发现它需要重定向到 HTTPS。 (mod_rewrite 是一种疯狂的模块,它可以以令人惊讶的方式处理各种事情。它可以进行路径转换、零碎的重写、创建子请求以及一堆其他疯狂的事情)。

我能想到的解决这个问题的方法很少。

    更改 http 站点的 vhosts 容器中的根目录,使其找不到密码文件(这是我的方法) 更改模块加载顺序,以便 mod_rewrite 在链中更早发生(可能会产生意想不到的后果) 使用setenvif

最后一个需要更多解释。还记得我告诉过你的那个斗式旅吗? Apache模块也可以设置环境变量,完全在module->module->module->chain之外。如果站点不是 HTTPS,您也许可以设置一个环境变量。然后,无论您如何设置访问控制,都可以使用 SetEnvIf 指令来始终允许访问已设置的资源,但您必须确保您将满足该重写规则。

正如我所说,我的选择是第一,但有时人们需要做一些疯狂的事情,而 Apache 会让你这样做。

这些天我对 https:// 网站的实际 SOP 是,我只是将我所有的 80 端口内容拍摄到一个根本无法提供任何内容的虚拟主机上。然后我通过 https://mod_rewrite 一切... badda bing, badda boom, 没有 http 也没有复杂的安全风险。

【讨论】:

以上是关于使用 .htaccess 将 http 重定向到 https 时,某些 url 出现奇怪的 401 错误的主要内容,如果未能解决你的问题,请参考以下文章

如何仅使用 .htaccess 将所有 url 重定向到 1 个 url

使用 .htaccess 将 http 重定向到 https 时,某些 url 出现奇怪的 401 错误

使用 .htaccess 将 www URL 重定向到 https 的非 www

使用 htaccess 将多个域(http https www 和非 www)重定向到新域

使用 htaccess 将请求 uri 重定向到镜像站点

http到https重定向使用htaccess在角度6中不起作用