CORS预检请求返回Access-Control-Allow-Origin:*,但浏览器仍然无法完成请求。

26

触发一个 AJAX GET 请求到 http://qualifiedlocalhost:8888/resource.json 会启动预期的 CORS 预检请求,看起来它回来了:

预检请求 OPTIONS

Request URL:http://qualifiedlocalhost:8888/resource.json
Request Method:OPTIONS
Status Code:200 OK

请求头

Accept:*/*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Access-Control-Request-Headers:accept, origin, x-requested-with
Access-Control-Request-Method:GET
Cache-Control:no-cache
Connection:keep-alive
Host:qualifiedlocalhost:8888
Origin:http://localhost:9000
Pragma:no-cache
Referer:http://localhost:9000/
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.71 Safari/537.36

响应头

Access-Control-Allow-Headers:Content-Type, X-Requested-With
Access-Control-Allow-Methods:GET,PUT,POST,DELETE
Access-Control-Allow-Origin:*
Connection:keep-alive
Content-Length:2
Content-Type:text/plain
Date:Thu, 01 Aug 2013 19:57:43 GMT
Set-Cookie:connect.sid=s%3AEpPytDm3Dk3H9V4J9y6_y-Nq.Rs572s475TpGhCP%2FK%2B2maKV6zYD%2FUg425zPDKHwoQ6s; Path=/; HttpOnly
X-Powered-By:Express

看起来不错吗?

所以应该可以工作,对吧?

但是后续的请求仍然失败,并显示错误信息:XMLHttpRequest cannot load http://qualifiedlocalhost:8888/resource.json. Origin http://localhost:9000 is not allowed by Access-Control-Allow-Origin.

真实请求

Request URL:http://qualifiedlocalhost:8888/resource.json

请求头部

Accept:application/json, text/plain, */*
Cache-Control:no-cache
Origin:http://localhost:9000
Pragma:no-cache
Referer:http://localhost:9000/
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.71 Safari/537.36
X-Requested-With:XMLHttpRequest

帮帮我!

也许答案就在我的眼前,但是你有什么想法吗?以防万一...我正在使用AngularJS的$resource并与CompoundJS服务器通信。


请参考以下网址了解如何在AngularJS资源中启用CORS请求:https://dev59.com/kWQo5IYBdhLWcg3wQNX4 - Christopher Marshall
谢谢。我的URL中有转义冒号。这些请求/响应快照来自Chrome开发工具,因此它们位于浏览器级别。 - tnunamak
1
如果Chrome阻止请求发送,则您的预检不会成功。因此,似乎您在这里遗漏了一些重要信息。此外,您的预检和实际GET请求似乎不匹配(不同的请求域和资源)。 - Ray Nicholus
你说得对,它们应该匹配。我使用了查找/替换功能,使我的问题更加通用,但是错过了一些内容,抱歉。有什么想法,我可能会遗漏哪些信息?预检OPTIONS请求返回HTTP 200和Access-Control-Allow-Origin:*。据我所知,这应该就是我需要的全部...我会继续挖掘。 - tnunamak
我确定这是浏览器中的一个错误。在我的情况下,服务器始终发送相同的CORS标头,但浏览器只会在其中一些上失败。正如我在下面的回复中详细描述的那样,响应中还有其他内容(可能是服务器设置的cookie),这暴露了浏览器的错误。我称之为错误,因为CORS规范中没有任何内容表明服务器不得为CORS客户端设置cookie。 - Shyam Habarakada
显示剩余3条评论
5个回答

15

将您的 Access-Control-Allow-Methods: 'GET, POST' 更改为 'GET, POST, PUT, DELETE'

更改前:

   app.use(function(req, res, next) {
        res.setHeader('Access-Control-Allow-Origin', '*');
        res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
        res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type, Authorization');
        next();
    });

之后:

app.use(function(req, res, next) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT ,DELETE');
    res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type, Authorization');
    next();
});

11
最近也遇到了同样的问题。问题在于 Access-Control-Allow-Origin 头(如果使用,还有 Access-Control-Allow-Credentials 头)必须在预检请求响应和实际请求响应中都要发送。
你的示例只在预检请求响应中发送了它。

在这种情况下,实际请求仍会发送到服务器并造成损害。对于浏览器来说,寻找正确的标头并防止结果显示已经被服务器处理的请求有何意义呢? - pippoflow

6
你的web服务器/获取函数是否还包括HTTP头:Access-Control-Allow-Origin?最终我通过加入这个头部,在使用AngularJS 1.0.7和远程Java servlet时获得了成功。以下是我的Java代码片段--在AngularJS客户端不需要进行任何更改:
Servlet:
@Override
protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // Send Response
    super.doOptions(request,  response);
    response.setHeader("Access-Control-Allow-Origin", "*");
    response.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Requested-With");
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    /* ... */
    response.setHeader("Access-Control-Allow-Origin", "*");
}

一个更加优雅的替代方案是使用servlet过滤器。

是的,在所有上述情况下,服务器都发送了正确的CORS头。 - Shyam Habarakada
这是我唯一的解决方法。resp.setHeader("Access-Control-Allow-Origin", "*"); - Rod Lima

4
我们注意到了相同的问题,服务器发送了正确的CORS标头,但浏览器会认为没有满足CORS要求而失败。有趣的是,在我们的情况下,它只发生在同一浏览器会话中的某些AJAX调用上,而不是全部。
工作假设...
清除浏览器缓存可以解决我们的问题——我目前的工作假定是这与跨域服务器设置的cookie有关。我注意到在您的场景中,有一个cookie作为对预检OPTIONS请求响应的一部分进行设置。您是否尝试过让该服务器不为来自不同来源的请求设置任何cookie?
然而,我们注意到在某些情况下,问题在重置浏览器后重新出现。此外,在私人模式下运行浏览器会导致问题消失,指向浏览器缓存中的某些问题。
供参考,以下是我的场景(我差点将其作为新问题发布在SO中,但改为放在这里):
我们遇到一个错误,即通过jQuery.ajax进行的一些CORS GET请求失败。在给定的浏览器会话中,我们看到预检OPTIONS请求先经过,然后是实际请求。在同一会话中,有些请求成功了,而另一些请求失败了。
浏览器网络控制台中请求和响应的顺序如下:先是OPTIONS预检请求。
OPTIONS /api/v1/users/337/statuses
HTTP/1.1 Host: api.obfuscatedserver.com 
Connection: keep-alive 
Access-Control-Request-Method: GET 
Origin: http://10.10.8.84:3003 
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36
Access-Control-Request-Headers: accept, origin, x-csrf-token, auth 
Accept: */* Referer: http://10.10.8.84:3003/ 
Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8

这会得到如下的响应:

HTTP/1.1 200 OK 
Date: Tue, 06 Aug 2013 19:18:22 GMT 
Server: Apache/2.2.22 (Ubuntu) 
Access-Control-Allow-Origin: http://10.10.8.84:3003 
Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT 
Access-Control-Max-Age: 1728000 
Access-Control-Allow-Credentials: true 
Access-Control-Allow-Headers: accept, origin, x-csrf-token, auth 
X-UA-Compatible: IE=Edge,chrome=1 
Cache-Control: no-cache 
X-Request-Id: 4429c4ea9ce12b6dcf364ac7f159c13c 
X-Runtime: 0.001344 
X-Rack-Cache: invalidate, pass 
X-Powered-By: Phusion 
Passenger 4.0.2 
Status: 200 OK 
Vary: Accept-Encoding
Content-Encoding: gzip

接下来是实际的GET请求:

GET https://api.obfuscatedserver.com/api/v1/users/337
HTTP/1.1 
Accept: application/json, text/javascript, */*; q=0.01 
Referer: http://10.10.8.84:3003/ 
Origin: http://10.10.8.84:3003 
X-CSRF-Token: xxxxxxx 
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36 
auth: xxxxxxxxx

在浏览器控制台中会出现类似以下错误信息:
XMLHttpRequest
  cannot load https://api.obfuscatedserver.com/api/v1/users/337.
  Origin http://10.10.8.84:3003 is not allowed by Access-Control-Allow-Origin.

额外的调试

我可以通过curl重放相同的序列,并且我能看到来自服务器的有效响应。即它们具有预期的CORS头,应该让请求通过。

在隐私浏览器窗口中运行相同的场景并没有复现问题。这使我尝试清除缓存,问题也消失了。但是过一段时间后,它又出现了。

这个问题在iPhone Safari上以及OSX桌面上的Chrome上都能够复现。

我真正需要帮助的是

我怀疑在跨域域中设置了一些cookie,这些cookie涉及到并可能暴露了浏览器中的一个bug。是否有工具或断点可以在浏览器(本地堆栈?)中设置,以进一步调试这个问题?在评估CORS策略的代码中设置断点将是理想的。


有人有关于如何进入Chrome并查看CORS请求失败时的操作技巧吗? - Shyam Habarakada
有人找到了更多关于这个问题的信息吗?我也遇到了同样的问题,但无法解决它。 - jdscolam
1
@jdscolam 我们无法让它正常工作,所以最终在源服务器上实现了代理,将适当的请求转发到跨域服务器。在我们的情况下,这些都是发送到我们API端点的XHR调用,并且可以根据传入的URL路径轻松映射这些调用。 - Shyam Habarakada
3
如果您的浏览器在没有跨域资源共享(CORS)或与不同来源的情况下请求资源,然后稍后再次请求相同的资源,则缓存的资源副本会具有来自原始请求的访问控制允许来源。如果多个子域从同一子域请求资源,则可能会出现这种情况。这些来源是不同的,都被允许,但是缓存的资源仅对最先请求它的子域有效。 - Mnebuerquo
@scipilot,听起来像是正确的答案。您应该将其作为新答案提交,以便它不会在我的非答案评论线程中丢失 :-) 谢谢。 - Shyam Habarakada
显示剩余2条评论

0

看起来你对“options”的响应工作正常。问题似乎出在“get”响应中。

你需要让你的“get”响应返回与“options”响应相同的CORS头信息。

特别是,“get”响应应该包括:

Access-Control-Allow-Headers:Content-Type, X-Requested-With
Access-Control-Allow-Methods:GET,PUT,POST,DELETE
Access-Control-Allow-Origin:*

1
只需要 Access-Control-Allow-Origin(如果使用,还有 Access-Control-Allow-Credentials)即可。 - BlueRaja - Danny Pflughoeft

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