在尝试从 REST API 获取数据时,请求的资源上不存在“访问控制-允许-源”标头

2022-08-29 22:10:04

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

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

抓取 API 无法加载 。对预检请求的响应未通过访问控制检查:请求的资源上不存在“访问控制-允许-源”标头。因此,不允许访问源“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”时,响应中的“访问控制-允许-源”标头的值不得为通配符“*”。因此,不允许访问源“http://127.0.0.1:3000”。由 XMLHttpRequest 发起的请求的凭据模式由 withCredentials 属性控制。


答案 1

这个答案涵盖了很多领域,所以它分为三个部分:

  • 如何使用 CORS 代理避免“无访问控制-允许-源标头”问题
  • 如何避免 CORS 印前检查
  • 如何修复“访问控制-允许-源标头不得为通配符”问题

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

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

您可以使用 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 预检请求的请求,这也有效,因为在这种情况下,代理还会发送使预检成功所需的 和 标头。OPTIONSAccess-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

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

它与一个小的curl脚本配合得很好 - 我得到我的数据。

要正确测试 ,您必须模拟浏览器发送的印前检查:curlOPTIONS

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"

...替换为您的实际URL。https://the.sign_in.urlsign_in

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

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

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

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

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

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

  • 如果服务器不需要请求标头,而是依赖于请求正文中嵌入的身份验证数据或作为查询参数AuthorizationPOST
  • 如果服务器不要求正文具有媒体类型,而是接受正文,因为参数名为(或其他参数),其值为 JSON 数据POSTContent-Type: application/jsonPOSTapplication/x-www-form-urlencodedjson

如何修复“访问控制-允许-源标头不得为通配符”问题

我收到另一条错误消息:

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

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

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

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

add_header Access-Control-Allow-Origin $http_origin

但这只是一个例子;其他(Web)服务器系统具有类似的方法来回显源值。


我使用的是 Chrome。我还尝试使用Chrome CORS插件

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

因此,即使用于测试,也要避免使用该插件。这只是一种分心。要测试您从服务器获得的响应,而无需浏览器过滤它们,最好使用如上所述。curl -H


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

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

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


答案 2

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

如果您正在托管Spring REST服务,那么您可以在Spring Framework中的博客文章CORS支持中找到它。

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

  1. 停止节点.js服务器。
  2. npm install cors --save
  3. 将以下行添加到服务器.js
const cors=require("cors");
const corsOptions ={
   origin:'*', 
   credentials:true,            //access-control-allow-credentials:true
   optionSuccessStatus:200,
}

app.use(cors(corsOptions)) // Use this after the variable declaration