协慌网

登录 贡献 社区

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

我正在尝试从 HP Alm 的 REST API 中获取一些数据。它与一个小的 curl 脚本一起使用时效果很好 - 我得到了我的数据。

现在使用 JavaScript 进行此操作,获取和 ES6(或多或少)似乎是一个更大的问题。我不断收到此错误消息:

提取 API 无法加载。对预检请求的响应未通过访问控制检查:在所请求的资源上不存在 “Access-Control-Allow-Origin” 标头。因此,不允许访问源 “ http://127.0.0.1:3000”。响应的 HTTP 状态码为 501。如果不透明的响应满足您的需求,请将请求的模式设置为 “no-cors”,以在禁用 CORS 的情况下获取资源。

我了解这是因为我试图从本地主机中获取数据,并且解决方案应使用 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 插件,但是随后出现另一条错误消息:

当请求的凭据模式为 “include” 时,响应中 “Access-Control-Allow-Origin” 标头的值不得为通配符 “*”。因此,不允许访问源 “ http://127.0.0.1:3000”。 XMLHttpRequest 发起的请求的凭据模式由 withCredentials 属性控制。

答案

该答案涉及很多领域,因此分为三个部分:

  • 如何使用 CORS 代理来解决“无访问控制 - 允许 - 来源标头” 的问题
  • 如何避免 CORS 飞行前
  • 如何解决“Access-Control-Allow-Origin 标头一定不能为通配符” 的问题

如何使用 CORS 代理来避免“无访问控制 - 允许 - 源标头”问题

如果您不控制服务器,您的前端代码正在向其发送请求,并且该服务器的响应问题仅在于缺少必要的Access-Control-Allow-Origin标头,那么您仍然可以使工作正常进行 - 通过 CORS 代理发出请求。

您可以使用 https://github.com/Rob--W/cors-anywhere / 中的代码轻松运行自己的代理。
您还可以使用以下 5 个命令在 2-3 分钟内轻松地将自己的代理部署到 Heroku:

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请求 - 确定服务器是否选择接收具有Authorization POSTContent-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。如果是其他状态代码,浏览器将在那里停止。

问题中的服务器正在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 标头一定不能为通配符” 的问题

我收到另一条错误消息:

当请求的凭据模式为 “include” 时,响应中 “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响应标头;例如,使用 nginx:

add_header Access-Control-Allow-Origin $http_origin

但这只是一个例子。其他(网络)服务器系统提供了类似的方法来回显原始值。


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

Chrome CORS 插件显然只是简单地将Access-Control-Allow-Origin: *标头注入浏览器看到的响应中。如果插件更聪明,那么它将把该伪造的Access-Control-Allow-Origin响应标头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-*标头是响应标头。您永远都不想在请求中发送它们。唯一的效果就是触发浏览器进行预检。

当客户端 URL 和服务器 URL 不匹配(包括端口号)时,将发生此错误。在这种情况下,您需要为跨源资源共享的 CORS 启用服务。

如果您托管的是 Spring REST 服务,则可以在博客文章 “ Spring Framework 中的 CORS 支持” 中找到它。

如果您正在使用 Node.js 服务器托管服务,则

  1. 停止 Node.js 服务器。
  2. npm install cors --save
  3. 将以下行添加到您的 server.js

    var cors = require('cors')
    
    app.use(cors()) // Use this after the variable declaration

出现问题是因为您在前端中添加了以下代码作为请求标头:

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

这些标头属于response ,而不是 request。因此,删除它们,包括以下行:

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

您的请求具有'Content-Type: application/json' ,因此触发了所谓的 CORS 预检。这导致浏览器使用 OPTIONS 方法发送了请求。有关详细信息,请参见CORS 飞行前检查。

因此,在后端,您必须通过返回包括以下内容的响应标头来处理此预检请求:

Access-Control-Allow-Origin : http://localhost:3000
Access-Control-Allow-Credentials : true
Access-Control-Allow-Methods : GET, POST, OPTIONS
Access-Control-Allow-Headers : Origin, Content-Type, Accept

当然,实际语法取决于您在后端使用的编程语言。

在您的前端,应该像这样:

function performSignIn() {
    let headers = new Headers();

    headers.append('Content-Type', 'application/json');
    headers.append('Accept', 'application/json');
    headers.append('Authorization', 'Basic ' + base64.encode(username + ":" +  password));
    headers.append('Origin','http://localhost:3000');

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