当试图从REST API获取数据时,请求的资源上没有 "Access-Control-Allow-Origin "头。

javascript api cors fetch-api preflight


我正在尝试从HP Alm的REST API中获取一些数据。用一个小的curl脚本就能很好地运行--我得到了我的数据。

现在用JavaScript、fetch和ES6(或多或少)来做,似乎是一个更大的问题。我一直收到这样的错误信息。

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

我理解这是因为我试图从我的localhost内部获取这些数据,解决的方法应该是使用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属性控制。




Answer 1 sideshowbarker


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

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

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

如果您不控制服务器,您的前端JavaScript代码正在向其发送请求,并且该服务器的响应问题仅在于缺少必要的 Access-Control-Allow-Origin 标头,那么您仍然可以使事情正常进行-通过CORS代理发出请求。为了展示它是如何工作的,首先这里是一些不使用CORS代理的代码:

const url = "https://example.com"; // site that doesn’t send Access-Control-*
fetch(url)
.then(response => response.text())
.then(contents => console.log(contents))
.catch(() => console.log("Can’t access " + url + " response. Blocked by browser?"))

导致 catch 块被击中的原因是,浏览器阻止该代码访问来自 https://example.com 的响应。而浏览器这样做的原因是,该响应缺少 Access-Control-Allow-Origin 响应标头。

现在,这是完全相同的示例,只是在其中添加了CORS代理:

const proxyurl = "https://cors-anywhere.herokuapp.com/";
const url = "https://example.com"; // site that doesn’t send Access-Control-*
fetch(proxyurl + url) // https://cors-anywhere.herokuapp.com/https://example.com
.then(response => response.text())
.then(contents => console.log(contents))
.catch(() => console.log("Can’t access " + url + " response. Blocked by browser?"))

注意:如果在尝试https://cors-anywhere.herokuapp.com时关闭或不可用,请参见下文,了解如何在2-3分钟内在Heroku上部署自己的CORS Anywhere服务器。

上面的第二个代码段可以成功访问响应,因为采用请求URL并将其更改为https://cors-anywhere.herokuapp.com/https://example.com(只需在其前面加上代理URL)即可请求通过该代理取得,然后:

  1. 将请求转发到 https://example.com
  2. https://example.com 接收响应。
  3. Access-Control-Allow-Origin 标头添加到响应中。
  4. 将该响应与添加的头传回给请求的前端代码。

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

您可以使用https://github.com/Rob--W/cors-anywhere/中的代码轻松运行自己的代理。
您也可以轻松地将自己的代理部署到Heroku,只需2-3分钟,5个命令,就可以轻松地部署到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前面加上 https://cors-anywhere.herokuapp.com ,而是在您自己的实例的URL 前面加上前缀;例如https://cryptic-headland-94862.herokuapp.com/https://example.com

因此,如果您尝试使用https://cors-anywhere.herokuapp.com时发现它已关闭(有时会出现故障),那么可以考虑获取一个Heroku帐户(如果您尚未使用)并拿2或花费3分钟完成上述步骤,以在Heroku上部署您自己的CORS Anywhere服务器。

无论您是运行自己的网站还是使用https://cors-anywhere.herokuapp.com或其他开放式代理,即使该请求触发浏览器执行CORS预检 OPTIONS 请求,该解决方案也都可以工作,因为在这种情况下,代理还会发回使预检成功所需的 Access-Control-Allow-HeadersAccess-Control-Allow-Methods 头。


如何避免CORS飞行前

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

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

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

“预检”的意思是:在浏览器尝试问题代码中的 POST 之前,它将首先向服务器发送 OPTIONS 请求-确定服务器是否选择接收包含以下内容的跨域 POSTAuthorizationContent-Type: application/json 标头。

用一个小的卷曲脚本,效果相当好--我得到了我的数据。

要使用 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标头一定不能为通配符”的问题

我又收到一条错误信息。

当请求的凭据模式为“ 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 的值。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-* 头是响应头。您永远不想在请求中发送它们。唯一的效果就是触发浏览器进行预检。




Answer 2 Rakesh


当客户端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



Answer 3 Lex Soft


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

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:应用程序/ 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));
}



Answer 4 Harrison O


在我的案例中,我用的是下面的解决方案

前端或角度

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);



Answer 5 Yair Levy


只是我的两分钱...关于如何使用CORS代理来解决“无 Access-Control-Allow-Origin 标头”问题

对于那些在后台使用php的人来说,部署 "CORS代理 "很简单,。

  1. 创建一个名为 "no-cors.php "的文件,内容如下。

    $URL = $_GET['url']; echo json_encode(file_get_contents($URL)); die();

  2. 在你的前端,做这样的事情:

    fetch('https://example.com/no-cors.php' + '?url=' + url) .then(response=>{*/Handle Response/*})