尝试从 REST API 获取数据时,请求的资源上不存在“Access-Control-Allow-Origin”标头

新手上路,请多包涵

我正在尝试从 HP Alm 的 REST API 中获取一些数据。它适用于一个小的 curl 脚本——我得到了我的数据。

现在使用 JavaScript、fetch 和 ES6(或多或少)似乎是一个更大的问题。我不断收到此错误消息:

Fetch API 无法加载。对预检请求的响应不

通过访问控制检查:没有“Access-Control-Allow-Origin”标头

出现在请求的资源上。来源 ‘ [ [ [http://127.0.0.1:3000’](http://127.0.0.1:3000’](http://127.0.0.1:3000’](http://127.0.0.1:3000’)](http://127.0.0.1:3000’](http://127.0.0.1:3000’](http://127.0.0.1:3000’](http://127.0.0.1:3000’))) 是

因此不允许访问。响应具有 HTTP 状态代码 501。

如果不透明的响应满足您的需求,请将请求的模式设置为

‘no-cors’ 来获取禁用 CORS 的资源。

我知道这是因为我试图从我的本地主机中获取该数据,并且解决方案应该使用 Cross-Origin Resource Sharing (CORS) 。我以为我确实做到了,但不知何故,它要么忽略了我在标题中写的内容,要么问题出在其他问题上。

那么,是否存在实施问题?我做错了吗?不幸的是,我无法检查服务器日志。我真的有点卡在这里。

function performSignIn() {

  let headers = new Headers();

  headers.append('Content-Type', 'application/json');
  headers.append('Accept', 'application/json');

  headers.append('Access-Control-Allow-Origin', 'http://localhost:3000');
  headers.append('Access-Control-Allow-Credentials', 'true');

  headers.append('GET', 'POST', 'OPTIONS');

  headers.append('Authorization', 'Basic ' + base64.encode(username + ":" + password));

  fetch(sign_in, {
      //mode: 'no-cors',
      credentials: 'include',
      method: 'POST',
      headers: headers
    })
    .then(response => response.json())
    .then(json => console.log(json))
    .catch(error => console.log('Authorization failed : ' + error.message));
}

我正在使用 Chrome。我也尝试使用该 Chrome CORS 插件,但随后我收到另一条错误消息:

响应中“Access-Control-Allow-Origin”标头的值

当请求的凭证模式为

‘包括’。因此不允许使用来源“ http://127.0.0.1:3000”

使用权。发起请求的凭证模式

XMLHttpRequest 由 withCredentials 属性控制。

原文由 daniel.lozynski 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 958
1 个回答

这个答案涵盖了很多领域,因此分为三个部分:

  • 如何使用 CORS 代理来避免 “No Access-Control-Allow-Origin 标头” 问题

  • 如何避免 CORS 预检

  • 如何解决 “Access-Control-Allow-Origin 标头不能是通配符” 问题


如何使用 CORS 代理来避免 “No Access-Control-Allow-Origin 标头” 问题

如果您不控制前端代码发送请求的服务器,而该服务器的响应问题只是缺少必要的 Access-Control-Allow-Origin 标头,您仍然可以让事情正常工作-通过 CORS 代理发出请求。

您可以使用 https://github.com/Rob–W/cors-anywhere/ 中的代码轻松运行自己的代理。

您还可以在 2-3 分钟内轻松地将自己的代理部署到 Heroku,只需 5 个命令:

 git clone https://github.com/Rob--W/cors-anywhere.git
 cd cors-anywhere/
 npm install
 heroku create
 git push heroku master

运行这些命令后,您将拥有自己的 CORS Anywhere 服务器,例如 https://cryptic-headland-94862.herokuapp.com/

现在,在您的请求 URL 前加上代理的 URL:

 https://cryptic-headland-94862.herokuapp.com/https://example.com

添加代理 URL 作为前缀会导致请求通过您的代理发出,其中:

  1. 将请求转发到 https://example.com

  2. 接收来自 https://example.com 的响应。

  3. Access-Control-Allow-Origin 标头添加到响应中。

  4. 将该响应连同添加的标头一起传递回请求的前端代码。

然后浏览器允许前端代码访问响应,因为带有 Access-Control-Allow-Origin 响应标头的响应是浏览器看到的。

即使请求是触发浏览器执行 CORS 预检 OPTIONS 请求的请求,这也有效,因为在这种情况下,代理还会发送进行预检所需的 Access-Control-Allow-HeadersAccess-Control-Allow-Methods 标头成功。


如何避免 CORS 预检

问题中的代码触发 CORS 预检——因为它发送了 Authorization 标头。

https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Preflighted_requests

即使没有, Content-Type: application/json 标头也会触发预检。

“预检”是什么意思:在浏览器尝试问题代码中的 POST 之前,它首先向服务器发送一个 OPTIONS 请求,以确定服务器是否选择接收具有 AuthorizationContent-Type: application/json 的跨域 POST Content-Type: application/json 标头。

它适用于一个小的 curl 脚本——我得到了我的数据。

要使用 curl 正确测试,您必须模拟浏览器发送的预检 OPTIONS

 curl -i -X OPTIONS -H "Origin: http://127.0.0.1:3000" \
 -H 'Access-Control-Request-Method: POST' \
 -H 'Access-Control-Request-Headers: Content-Type, Authorization' \
 "https://the.sign_in.url"

…将 https://the.sign_in.url sign_in 为您的实际登录 URL。

浏览器需要来自该 OPTIONS 请求的响应必须具有如下标头:

 Access-Control-Allow-Origin: http://127.0.0.1:3000
 Access-Control-Allow-Methods: POST
 Access-Control-Allow-Headers: Content-Type, Authorization

如果 OPTIONS 响应不包含这些标头,则浏览器将停在那里并且永远不会尝试发送 POST 请求。此外,响应的 HTTP 状态码必须是 2xx——通常是 200 或 204。如果是任何其他状态码,浏览器就会停在那里。

问题中的服务器使用 501 状态代码响应 OPTIONS 请求,这显然意味着它试图表明它不实现对 OPTIONS 请求的支持。在这种情况下,其他服务器通常会以 405“方法不允许”状态代码进行响应。

因此,如果服务器使用 405 或 501 或 200 或 204 以外的任何内容响应该 OPTIONS 请求,或者如果没有响应那些必要的请求,您将永远无法从前端 JavaScript 代码直接向该服务器发出 POST 请求响应头。

避免为问题中的案例触发预检的方法是:

  • 如果服务器不需要 Authorization 请求标头,而是依赖于嵌入在 POST 请求正文中的身份验证数据或作为查询参数

  • 如果服务器不要求 POST 正文具有 Content-Type: application/json 媒体类型,而是接受 POST 正文为 application/x-www-form-urlencoded 并带有名为 json (或其他)的参数,其值为JSON数据


如何解决 “Access-Control-Allow-Origin 标头不能是通配符” 问题

我收到另一条错误消息:

响应中“Access-Control-Allow-Origin”标头的值

当请求的凭证模式为

‘包括’。因此不允许使用来源“ http://127.0.0.1:3000

使用权。发起请求的凭证模式

XMLHttpRequest 由 withCredentials 属性控制。

对于具有凭据的请求,如果 Access-Control-Allow-Origin 标头的值为 * ,浏览器将不会让您的前端 JavaScript 代码访问响应。相反,这种情况下的值必须与前端代码的来源 http://127.0.0.1:3000 完全匹配。

请参阅 MDN HTTP 访问控制 (CORS) 文章中的 凭据请求和通配符

如果您控制要向其发送请求的服务器,处理这种情况的常用方法是将服务器配置为获取 Origin 请求标头的值,并将其回显/反射回 Access-Control-Allow-Origin 的值 Access-Control-Allow-Origin 响应头;例如,使用 nginx:

 add_header Access-Control-Allow-Origin $http_origin

但这只是一个例子;其他(网络)服务器系统也有类似的方式来回显原始值。


我正在使用 Chrome。我也尝试使用那个 Chrome CORS 插件

Chrome CORS 插件显然只是简单地将 Access-Control-Allow-Origin: * 标头注入到浏览器看到的响应中。如果插件更智能,它会做的是将那个虚假 Access-Control-Allow-Origin 响应标头的值设置为前端 JavaScript 代码的实际来源 http://127.0.0.1:3000

所以避免使用那个插件,即使是为了测试。这只是一种分心。要在没有浏览器过滤的情况下测试从服务器获得的响应,最好使用上面的 curl -H


至于问题中 fetch(…) 请求的前端 JavaScript 代码:

 headers.append('Access-Control-Allow-Origin', 'http://localhost:3000');
 headers.append('Access-Control-Allow-Credentials', 'true');

删除这些行。 Access-Control-Allow-* 标头是 响应 标头。你永远不想在请求中发送它们。这样做的唯一效果是触发浏览器进行预检。

原文由 sideshowbarker 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题