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

IT技术 javascript cors fetch-api preflight
2020-12-28 00:20:16

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

现在使用 JavaScript、fetch 和 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 插件,但后来我收到另一条错误消息:

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

6个回答

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

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

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

如果您不控制前端代码向其发送请求的服务器,并且来自该服务器的响应的问题只是缺少必要的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服务器发送请求,以确定服务器是否选择接收POST具有AuthorizationContent-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_inURL。

浏览器从该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。如果是任何其他状态代码,浏览器将在那里停止。

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

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

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

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

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

我收到另一条错误消息:

当请求的凭据模式为 “包含”http://127.0.0.1:3000 时,响应中“Access-Control-Allow-Origin”标头的值不能是通配符“* ”。因此不允许访问 源“ 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响应标头的值设置为前端 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-*头是响应头。你永远不想在请求中发送它们。唯一的效果是触发浏览器进行预检。

一个常见问题的奇妙答案。FWIW,当向请求添加“授权:承载”标头时,我们让服务器响应 405(不支持的方法),因此修复是将令牌移动到 POST 字段而不是作为对 OPTIONS 检查的服务器响应的控制在客户给出的时间范围内是不可能的。
2021-02-08 00:20:16
https://cors-anywhere.herokuapp.com/现在已经不能用了。客户端现在将收到一个403 Forbidden错误 - 除非开发人员明确要求暂时通过。这是公告:github.com/Rob--W/cors-anywhere/issues/301我建议简单地cors-anywhere从答案中删除参考,因为它不再有用。
2021-02-13 00:20:16
@BoghyonHoffmann 非常感谢您的提醒。我已经相应地修改了答案 - 如果您认为需要进一步更改,请查看并告诉我。
2021-02-24 00:20:16
精湛的答案,我的问题是远程服务器没有响应OPTIONS请求,所以摆弄有什么好像我的年龄通过移除头解决了它的请求和头后Content-Type,并Access-Control-Allow-Origin-谢谢!
2021-03-04 00:20:16
实际上,将新版本的扩展上传到 Azure DevOps 后,您必须在dev.azure.com {your_organization}/_settings/extensions?tab=installed 添加新范围时更新它的权限"scopes": ["vso .build"] 在清单文件中。
2021-03-09 00:20:16

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

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

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

  1. 停止 Node.js 服务器。
  2. npm install cors --save
  3. 将以下行添加到您的 server.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
这个问题实际上是关于提出请求的。那么假设您无权访问 REST 服务器?没有办法提出请求吗?
2021-02-14 00:20:16
拜托,我可以请你支持这个stackoverflow.com/questions/67963038/...
2021-02-19 00:20:16
including the port number :(
2021-03-01 00:20:16
注意:您还需要mode: 'no-cores'fetch() 客户端选项中删除您的设置除非mode: 'cores'在您的请求中,否则不会发送您的 JSON 正文
2021-03-07 00:20:16

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

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));
}
这应该是最佳答案 - 我真的不喜欢绕过 CORS 的想法,尤其是通过第三方路由它。
2021-02-21 00:20:16
@mitsu 如果你参考这一行: let headers = new Headers(); 上面,那么它是 fetch API 的接口,用于对 http 请求或响应头进行操作。访问developer.mozilla.org/en-US/docs/Web/API/Headers获取详细信息以及使用示例。
2021-02-26 00:20:16
嘿,请问'Header()' 是什么?
2021-03-04 00:20:16

就我而言,我使用以下解决方案

前端或角度

post(
    this.serverUrl, dataObjToPost,
    {
      headers: new HttpHeaders({
           'Content-Type':  'application/json',
         })
    }
)

后端(我使用php)

header("Access-Control-Allow-Origin: http://localhost:4200");
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header("Access-Control-Allow-Headers: Content-Type, Authorization");

$postdata = file_get_contents("php://input");
$request = json_decode($postdata);
print_r($request);

使用dataType: 'jsonp'对我有用。

   async function get_ajax_data(){
       var _reprojected_lat_lng = await $.ajax({
                                type: 'GET',
                                dataType: 'jsonp',
                                data: {},
                                url: _reprojection_url,
                                error: function (jqXHR, textStatus, errorThrown) {
                                    console.log(jqXHR)
                                },
                                success: function (data) {
                                    console.log(data);

                                    // note: data is already json type, you
                                    //       just specify dataType: jsonp
                                    return data;
                                }
                            });


 } // function