请求 REST API 获取数据时出现“未在请求的资源上设置'Access-Control-Allow-Origin'头”错误

1243

我正在尝试从HP Alm的REST API获取一些数据。使用一个小的curl脚本可以很好地工作 - 我得到了我的数据。

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

Fetch 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插件,但是我收到了另一个错误消息:

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

32个回答

1719
这个答案涵盖了很多内容,所以分为三个部分:
  • 如何使用CORS代理来避免“没有Access-Control-Allow-Origin头部”的问题
  • 如何避免CORS预检
  • 如何修复“Access-Control-Allow-Origin头部不能是通配符”的问题

如何使用CORS代理来避免“没有Access-Control-Allow-Origin头部”的问题 如果您无法控制您的前端代码发送请求的服务器,并且来自该服务器的响应问题仅仅是缺少必要的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请求,以确定服务器是否选择接收具有AuthorizationContent-Type: application/json头部的跨源POST

使用一个小的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。如果是其他状态码,浏览器将在那里停止。 问题中的服务器以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 header must not be the wildcard”问题 我收到了另一个错误信息: 响应中“Access-Control-Allow-Origin”头的值在请求的凭据模式为“include”时不能是通配符“*”。因此,源“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响应头部的值设置为你前端JavaScript代码的实际来源,即http://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-* 头是响应头。您不希望在请求中发送它们。这样做的唯一效果是触发浏览器执行预检。

28
非常好的回答,我的问题是远程服务器没有响应 OPTIONS 请求,所以在尝试了一段时间的请求和头文件之后,我通过删除 Content-TypeAccess-Control-Allow-Origin 头文件来解决了这个问题 - 谢谢! - Morvael
1
常见问题的绝佳答案。顺便说一下,当向请求添加“Authorization: Bearer”头时,我们的服务器响应了405(方法不受支持),所以修复方法是将令牌移动到POST字段中,因为在客户端给出的时间范围内无法控制服务器对OPTIONS检查的响应。 - Beerswiller
2
实际上,在将扩展的新版本上传到Azure DevOps之后,您必须在https://dev.azure.com/{your_organization}/_settings/extensions?tab=installed中更新其权限,当您在清单文件中添加新的范围“scopes”: [“vso.build”]时。 - Rodrigo Cipriani da Rosa
9
https://cors-anywhere.herokuapp.com/现在不能再使用了。客户端将会收到一个403 Forbidden错误 - 除非开发者明确请求临时通过。这是官方公告:https://github.com/Rob--W/cors-anywhere/issues/301。我建议从回答中直接删除`cors-anywhere`的相关内容,因为它已经没有用处了。 - Boghyon Hoffmann
“xxx-Credentials: true” 部分已更新。我认为它被停止以允许访问 CORS 调用,当 -Origin 设置匹配浏览器 URL 但不同于 HTTP 调用端点时。 - Vincent
显示剩余3条评论

227

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

如果您正在托管Spring REST服务,则可以在博客文章CORS support in Spring Framework中找到它。

如果您使用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

14
包括端口号。 - scottysseus
2
注意:您还需要从客户端的fetch()选项中删除mode: 'no-cores'设置。除非在请求中使用mode: 'cores',否则您的JSON主体将不会被发送。 - Kyle Baker
2
这个问题实际上是关于发出请求的。所以假设您没有访问REST服务器?难道没有办法发出请求吗? - Jamie Marshall
他是绝对正确的。谢谢大家。 - Anshuman Bisoyi

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

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

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

这些标题属于响应,而不是请求。所以删除它们,包括这行:


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

41
这应该是最佳答案——我非常不喜欢绕过 CORS 的想法,特别是通过第三方路由它。 - DaveMongoose
1
嘿,'Header()'是什么意思? - mitsu
2
如果你看到上面的代码行:let headers = new Headers();,那么它是fetch API的一个接口,用于处理HTTP请求或响应头。请访问https://developer.mozilla.org/en-US/docs/Web/API/Headers获取详细信息以及使用示例。@mitsu - Lex Soft

27

就我的情况而言,我使用以下解决方案。

前端或Angular

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

11

使用 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               

11

在API中添加mode:no-cors可以避免CORS问题。

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

7
出现错误:“Uncaught (in promise) SyntaxError: Unexpected end of input” - Chris Tang
当我在我的PUT请求中添加 mode:no-cors 后,出现了以下错误: TypeError: Failed to execute 'fetch' on 'Window': 'PUT' is unsupported in no-cors mode. 因此,如果你想知道的话,似乎这对于PUT不起作用。 - anthonyjdella

9
如果你的API是用ASP.NET Core编写的,请按照以下步骤操作:
  • Install the Microsoft.AspNetCore.Cors package.

  • Add the below line in the ConfigureServices method in file Startup.cs:

    services.AddCors();
    
  • Add the below line in the Configure method in file startup.cs:

    app.UseCors(options =>
         options.WithOrigins("http://localhost:8080")
                .AllowAnyHeader()
                .AllowAnyMethod());
    
  • Make sure you add this after - app.UseRouting();

    Refer to the below image(from MSDN) to see the middleware order:

    https://istack.dev59.com/vQ4yT.webp


2
在UseRouting之前添加?你确定吗?所有其他参考资料都说相反,例如:查看 - mkb

8

就我个人而言… 关于如何使用CORS代理解决“没有Access-Control-Allow-Origin标头”问题:

对于那些在后端使用php的人来说,部署“CORS代理”非常简单:

  1. create a file named 'no-cors.php' with the following content:

    $URL = $_GET['url'];
    echo json_encode(file_get_contents($URL));
    die();
    
  2. on your front end, do something like:

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

7
可能导致CORS问题的原因:
  • 检查您服务器端的 access header:请参考此链接

  • 检查浏览器从服务器接收到的请求头。下图显示了这些头文件:

    Enter image description here

  • 如果您正在使用fetch方法并尝试访问跨源请求,请确保有mode:cors。请参考此链接

  • 有时候,如果程序出现问题,也会导致CORS问题出现,请确保您的代码正常工作。

  • 确保在API中处理OPTION方法。


6
在我的React / Express应用程序中遇到了这个问题。在server.js(或您的服务器文件名)中添加以下代码解决了我的问题。安装cors然后:
const cors = require('cors');
app.use(cors({
    origin: 'http://example.com', // use your actual domain name (or localhost), using * is not recommended
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Origin', 'X-Requested-With', 'Accept', 'x-client-key', 'x-client-token', 'x-client-secret', 'Authorization'],
    credentials: true
}))

现在你可以直接从前端进行API调用,无需传递任何额外的参数。


我是React的新手,由于有机会构建一个React应用程序,想知道如何确定我的服务器文件名是什么? - agaonsindhe
在您的后端目录中,您可以搜索整个目录以查找 .listen 方法,这很可能是服务器文件在特定端口上侦听/运行您的后端的方法。 - first

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,