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

2022-01-15 00:00:00 cors javascript fetch-api preflight

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

I'm trying to fetch some data from the REST API of HP Alm. It works pretty well with a small curl script - I get my data.

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

Now doing that with JavaScript, fetch and ES6 (more or less) seems to be a bigger issue. I keep getting this error message:

Fetch API 无法加载.对预检请求的响应不通过访问控制检查:没有Access-Control-Allow-Origin"标头出现在请求的资源上.来源 'http://127.0.0.1:3000' 是因此不允许访问.响应具有 HTTP 状态代码 501.如果不透明的响应满足您的需求,请将请求的模式设置为'no-cors' 来获取禁用 CORS 的资源.

Fetch API cannot load . Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:3000' is therefore not allowed access. The response had HTTP status code 501. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

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

I understand that this is because I am trying to fetch that data from within my localhost and the solution should be using CORS. Now I thought I actually did that, but somehow it either ignores what I write in the header or the problem is something else?

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

So, is there an implementation issue? Am I doing it wrong? I can't check the server logs unfortunately. I'm really a bit stuck here.

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 插件,但随后我收到另一条错误消息:

I am using Chrome. I also tried using that Chrome CORS Plugin, but then I am getting another error message:

响应中Access-Control-Allow-Origin"标头的值当请求的凭证模式为'包括'.因此,不允许使用来源 'http://127.0.0.1:3000'使用权.发起请求的凭证模式XMLHttpRequest 由 withCredentials 属性控制.

The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. Origin 'http://127.0.0.1:3000' is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

推荐答案

这个答案涉及很多方面,所以分为三个部分:

This answer covers a lot of ground, so it’s divided into three parts:

  • 如何使用 CORS 代理解决No Access-Control-Allow-Origin 标头"问题
  • 如何避免 CORS 预检
  • 如何解决Access-Control-Allow-Origin 标头不能是通配符"问题

如何使用 CORS 代理来避免No Access-Control-Allow-Origin header"问题

How to use a CORS proxy to avoid "No Access-Control-Allow-Origin header" problems

如果您不控制前端代码向其发送请求的服务器,则该服务器的响应问题只是缺少必要的 Access-Control-Allow-Origin标头,您仍然可以通过 CORS 代理发出请求.

If you don’t control the server your frontend code is sending a request to, and the problem with the response from that server is just the lack of the necessary Access-Control-Allow-Origin header, you can still get things to work—by making the request through a CORS proxy.

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

You can easily run your own proxy with code from https://github.com/Rob--W/cors-anywhere/.
You can also easily deploy your own proxy to Heroku in just 2-3 minutes, with 5 commands:

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/.

After running those commands, you’ll end up with your own CORS Anywhere server running at, e.g., https://cryptic-headland-94862.herokuapp.com/.

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

Now, prefix your request URL with the URL for your proxy:

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

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

Adding the proxy URL as a prefix causes the request to get made through your proxy, which:

  1. 将请求转发到 https://example.com.
  2. 接收来自 https://example.com 的响应.
  3. Access-Control-Allow-Origin 标头添加到响应中.
  4. 将该响应连同添加的标头传递回请求的前端代码.
  1. Forwards the request to https://example.com.
  2. Receives the response from https://example.com.
  3. Adds the Access-Control-Allow-Origin header to the response.
  4. Passes that response, with that added header, back to the requesting frontend code.

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

The browser then allows the frontend code to access the response, because that response with the Access-Control-Allow-Origin response header is what the browser sees.

即使请求是触发浏览器执行 CORS 预检 OPTIONS 请求的请求,这仍然有效,因为在这种情况下,代理也会发送 Access-Control-Allow-Headers<使预检成功所需的/code> 和 Access-Control-Allow-Methods 标头.

This works even if the request is one that triggers browsers to do a CORS preflight OPTIONS request, because in that case, the proxy also sends the Access-Control-Allow-Headers and Access-Control-Allow-Methods headers needed to make the preflight succeed.

如何避免 CORS 预检

问题中的代码触发 CORS 预检 - 因为它发送 Authorization 标头.

The code in the question triggers a CORS preflight—since it sends an Authorization header.

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

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

Even without that, the Content-Type: application/json header will also trigger a preflight.

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

What "preflight" means: before the browser tries the POST in the code in the question, it first sends an OPTIONS request to the server, to determine if the server is opting-in to receiving a cross-origin POST that has Authorization and Content-Type: application/json headers.

一个小的 curl 脚本可以很好地工作 - 我得到了我的数据.

It works pretty well with a small curl script - I get my data.

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

To properly test with curl, you must emulate the preflight OPTIONS the browser sends:

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.

…with https://the.sign_in.url replaced by whatever your actual sign_in URL is.

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

The response the browser needs from that OPTIONS request must have headers like this:

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.如果是其他任何状态码,浏览器就会停在那里.

If the OPTIONS response doesn’t include those headers, the browser will stop right there and never attempt to send the POST request. Also, the HTTP status code for the response must be a 2xx—typically 200 or 204. If it’s any other status code, the browser will stop right there.

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

The server in the question responds to the OPTIONS request with a 501 status code, which apparently means it’s trying to indicate it doesn’t implement support for OPTIONS requests. Other servers typically respond with a 405 "Method not allowed" status code in this case.

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

So you’ll never be able to make POST requests directly to that server from your frontend JavaScript code if the server responds to that OPTIONS request with a 405 or 501 or anything other than a 200 or 204 or if doesn’t respond with those necessary response headers.

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

The way to avoid triggering a preflight for the case in the question would be:

  • 如果服务器不需要 Authorization 请求标头,而是依赖嵌入在 POST 请求正文中或作为查询参数的身份验证数据
  • 如果服务器不要求 POST 正文具有 Content-Type: application/json 媒体类型,而是接受 POST 正文为 application/x-www-form-urlencoded,带有一个名为 json(或其他)的参数,其值为 JSON 数据
  • if the server didn’t require an Authorization request header but instead, e.g., relied on authentication data embedded in the body of the POST request or as a query param
  • if the server didn’t require the POST body to have a Content-Type: application/json media type but instead accepted the POST body as application/x-www-form-urlencoded with a parameter named json (or whatever) whose value is the JSON data

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

How to fix "Access-Control-Allow-Origin header must not be the wildcard" problems

我收到另一条错误消息:

响应中Access-Control-Allow-Origin"标头的值当请求的凭据模式为时,不能是通配符'*''包括'.因此不允许使用 Origin 'http://127.0.0.1:3000'访问.由发起的请求的凭证模式XMLHttpRequest 由 withCredentials 属性控制.

The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. Origin 'http://127.0.0.1:3000' is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

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

For requests that have credentials, browsers won’t let your frontend JavaScript code access the response if the value of the Access-Control-Allow-Origin header is *. Instead the value in that case must exactly match your frontend code’s origin, http://127.0.0.1:3000.

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

See Credentialed requests and wildcards in the MDN HTTP access control (CORS) article.

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

If you control the server you’re sending the request to, a common way to deal with this case is to configure the server to take the value of the Origin request header, and echo/reflect that back into the value of the Access-Control-Allow-Origin response header; e.g., with nginx:

add_header Access-Control-Allow-Origin $http_origin

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

But that’s just an example; other (web) server systems have similar ways to echo origin values.

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

I am using Chrome. I also tried using that Chrome CORS Plugin

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

That Chrome CORS plugin apparently just simplemindedly injects an Access-Control-Allow-Origin: * header into the response the browser sees. If the plugin were smarter, what it would be doing is setting the value of that fake Access-Control-Allow-Origin response header to the actual origin of your frontend JavaScript code, http://127.0.0.1:3000.

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

So avoid using that plugin, even for testing. It’s just a distraction. To test what responses you get from the server with no browser filtering them, you’re better off using curl -H as above.

就问题中 fetch(...) 请求的前端 JavaScript 代码而言:

As far as the frontend JavaScript code for the fetch(…) request in the question:

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

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

Remove those lines. The Access-Control-Allow-* headers are response headers. You never want to send them in requests. The only effect of that is to trigger a browser to do a preflight.

相关文章