为什么Chrome中已验证的CORS请求的预检OPTIONS请求可以工作,而Firefox不能?

74

我正在编写一个JavaScript客户端,可以包含在第三方网站中(类似Facebook的“赞”按钮)。它需要从需要基本HTTP身份验证的API中检索信息。简化的设置如下:

第三方站点在其页面上包含此代码段:

<script 
async="true"
id="web-dev-widget"
data-public-key="pUbl1c_ap1k3y"
src="http://web.dev/widget.js">
</script>

widget.js 调用了 API:

var el = document.getElementById('web-dev-widget'),
    user = 'token',
    pass = el.getAttribute('data-public-key'),
    url = 'https://api.dev/',
    httpRequest = new XMLHttpRequest(),
    handler = function() {
      if (httpRequest.readyState === 4) {
        if (httpRequest.status === 200) {
          console.log(httpRequest.responseText);
        } else {
          console.log('There was a problem with the request.', httpRequest);
        }
      }
    };

httpRequest.open('GET', url, true, user, pass);
httpRequest.onreadystatechange = handler;
httpRequest.withCredentials = true;
httpRequest.send();

API已配置为响应适当的标题:

Header set Access-Control-Allow-Credentials: true
Header set Access-Control-Allow-Methods: "GET, OPTIONS"
Header set Access-Control-Allow-Headers: "origin, authorization, accept"
SetEnvIf Origin "http(s)?://(.+?\.[a-z]{3})$" AccessControlAllowOrigin=$0
Header set Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin

请注意,Access-Control-Allow-Origin被设置为Origin而不是使用通配符,因为我正在发送带凭据的请求(withCredentials)。

现在一切就位,可以进行异步跨域身份验证请求,在OS X 10.8.2的Chrome 25中效果很好。在开发工具中,我可以看到网络请求GET请求之前OPTIONS请求,响应按预期返回。

在Firefox 19中进行测试时,Firebug中没有向API发送网络请求,并在控制台记录了以下错误:NS_ERROR_DOM_BAD_URI: Access to restricted URI denied

经过深入挖掘,我发现根据评论,Gecko不允许用户名和密码直接在跨站点URI中。我假设这是由于使用open()的可选用户和密码参数,因此我尝试了另一种方法来进行身份验证请求,即将凭证进行Base64编码并在Authorization标头中发送:

// Base64 from http://www.webtoolkit.info/javascript-base64.html
auth = "Basic " + Base64.encode(user + ":" + pass);

...
// after open() and before send()
httpRequest.setRequestHeader('Authorization', auth);

这会导致OPTIONS请求的401 Unauthorized响应,引发像“为什么在Chrome中可以工作而Firefox不行?”这样的谷歌搜索!那时我知道自己遇到麻烦了。

为什么在Chrome中可以工作而Firefox不行?如何让OPTIONS请求发送和响应始终一致?


我很乐意听取关于如何改进问题的建议。 - maxbeatty
4个回答

148

为什么它在Chrome中可以工作而在Firefox中不行?

CORS预检请求的W3规范清楚地指出应该排除用户凭据。但是,在ChromeWebKit中存在一个错误,即返回401状态的OPTIONS请求仍会发送后续请求。

Firefox有一个相关的错误文件,其中包含一个链接到W3公共Web应用程序邮件列表的请求,要求更改CORS规范,以允许身份验证标头在OPTIONS请求中发送,以使IIS用户受益。基本上,他们正在等待那些服务器过时。

我该如何使OPTIONS请求的发送和响应保持一致?
只需让服务器(例如 API)在不需要身份验证的情况下响应OPTIONS请求即可。 Kinvey 在这方面做得很好,同时还链接到了Twitter API 的一个问题,有趣的是在任何浏览器问题被提交之前几周就已经概述了这个困境。

2
我应该总是为OPTIONS请求提供相同的响应,还是应该根据所请求的资源而定?如果取决于资源,攻击者可以使用OPTIONS请求来发现服务器内容/URL和支持该资源的功能。请参见此问题:https://dev59.com/a2Ii5IYBdhLWcg3w8AFy - IT Hit WebDAV
29
不进行OPTION请求身份验证会存在安全风险吗? - Kevin Meredith
14
对于那些来到这里的人:值得使用 curl -X OPTIONS http://yourdomain.example.com 来查看你的服务器对 OPTIONS 请求做出的响应。同时确保彻底清除浏览器缓存,因为 Firefox 会强制缓存这些响应,所以即使你已经更改了服务器配置,它仍然会报告相同的错误。 - toxaq
这对我来说是一个非常困难的发现过程。我不确定为什么要花这么长时间才找到这个答案,但了解“阻止cookie标志”以及它适用于“预检请求”,帮助我理解OPTIONS请求不会发送cookies - Corey Alix
1
我真的很想得到@KevinMeredith提出的问题的答案...如果不要求对OPTIONS请求进行身份验证会有哪些安全风险? - heez
显示剩余7条评论

10

这篇文章虽然比较旧,但可能对于解决CORS问题有所帮助。为完成基本授权问题,您应避免在服务器上对OPTIONS请求进行授权。以下是一个Apache配置示例,只需要在您的VirtualHost或Location中添加类似如下内容即可:

<LimitExcept OPTIONS>
    AuthType Basic
    AuthName <AUTH_NAME>
    Require valid-user
    AuthUserFile <FILE_PATH>
</LimitExcept>

这是单一真正好的答案 -- 谢谢 !!!!! --- 建议 --- SetEnvIf Origin "^(.*?)$" origin_is=$0 Header always set Access-Control-Allow-Origin %{origin_is}e env=origin_is - bortunac

0
只是为了记录,原因在另一个答案中有很好的解释,同时Firefox 87+提供了一个解决方法,最初在这里解释过。
自从Firefox 87(于2021年3月发布)以来,可以在about:config中设置以下首选项,即Firefox配置编辑器:
network.cors_preflight.allow_client_cert: true

Firefox for Enterprise 87 - Release notes中:

使用TLS客户端证书的企业可以切换network.cors_preflight.allow_client_cert偏好设置,以获得与Google Chrome兼容的CORS协议处理方式。特别是,与Fetch标准相反,这将在CORS预检请求中传输TLS客户端证书。有关更多信息,请参阅bug 1511151

参考资料:


0

这对我来说很特别。我发送了一个名为“SESSIONHASH”的头信息。对于Chrome和Opera没有问题,但Firefox也希望在“Access-Control-Allow-Headers”列表中包含此头信息。否则,Firefox将抛出CORS错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接