为什么我的JavaScript代码会收到“请求的资源上没有'Access-Control-Allow-Origin'头”错误,而Postman却没有?

3289
修改说明: 这个问题是关于为什么浏览器上的 XMLHttpRequest/fetch/等受到同源策略限制(你会收到 CORB 或 CORS 错误),而 Postman 却没有。这个问题不是关于如何修复 "No 'Access-Control-Allow-Origin'..." 错误,而是关于为什么它们会发生。
停止发布以下内容
  • 针对每种语言/框架的CORS配置。请查找相关语言/框架的问题
  • 允许请求规避CORS的第三方服务
  • 用于关闭各种浏览器的CORS的命令行选项

我正在尝试使用JavaScript连接到内置FlaskRESTfulAPI来进行授权。然而,当我发出请求时,我收到以下错误:

XMLHttpRequest cannot load http://myApiUrl/login. 
No 'Access-Control-Allow-Origin' header is present on the requested resource. 
Origin 'null' is therefore not allowed access.

我知道 API 或远程资源必须设置标头,但为什么我通过 Chrome 扩展程序 Postman 发出请求时它能够工作?

这是请求代码:

$.ajax({
  type: 'POST',
  dataType: 'text',
  url: api,
  username: 'user',
  password: 'pass',
  crossDomain: true,
  xhrFields: {
    withCredentials: true,
  },
})
  .done(function (data) {
    console.log('done');
  })
  .fail(function (xhr, textStatus, errorThrown) {
    alert(xhr.responseText);
    alert(textStatus);
  });

56
你是从本地主机发出请求还是直接执行HTML? - MD. Sahib Bin Mahboob
2
如果我理解你的问题,我从本地主机发出请求 - 我在我的电脑上有一个页面,只需运行它。当我将网站部署到托管上时,它会给出相同的结果。 - Mr Jedi
16
若有人想要阅读更多相关资料,MDN网站上有一篇关于ajax和跨域请求的好文章:https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS - Sam Eaton
1
这个问题的答案(现在已被删除,只能被 10K 用户看到)是元问题 为什么这个被点赞的答案被删除了一次,重新发布后又被删除了? 的主题。 - Peter Mortensen
这里有一个相关的 CORS 深入研究,涉及到 S3/Cloudfront 的缓存和标头触发它的问题:https://dev59.com/p-k5XIcBkEYKwwoY9-t1. - OG Sean
15个回答

1658
如果我理解正确,您正在对不同于您的页面所在域的XMLHttpRequest进行操作。因此,出于安全原因,浏览器会阻止它,通常允许在相同来源的请求。当您想要进行跨域请求时,需要采取不同的方法。
当您使用Postman时,他们不受此策略限制。引用自Cross-Origin XMLHttpRequest

常规网页可以使用XMLHttpRequest对象与远程服务器发送和接收数据,但它们受同源策略的限制。扩展程序则没有这种限制。只要首先请求跨域权限,扩展程序就可以与其来源外的远程服务器通信。


249
浏览器没有阻挡请求。唯一会完全阻挡跨域ajax请求的浏览器是IE7或更早版本。所有浏览器(除了IE7及更早版本外)都实现了CORS规范(IE8和IE9部分支持)。您只需要通过返回基于请求的正确标头来选择加入CORS请求。您应该在http://mzl.la/VOFrSz上学习CORS概念。Postman也通过XHR发送请求。如果您在使用Postman时没有看到相同的问题,则意味着您在不知情的情况下未经过同样的Postman请求。 - Ray Nicholus
21
Postman不是从你的Java/Python代码发送请求,而是直接从浏览器发送请求。在涉及跨域请求时,Chrome扩展程序中的XHR工作方式略有不同。 - Ray Nicholus
1
在此帖子中找到了一个详细的示例:https://dev59.com/YL_qa4cB1Zd3GeqPOsAO - Rayed
只有当某些请求参数等于10时,才会在本地发生这种情况,哈哈?在其他所有情况下都正常工作。这是怎么回事?:D - undefined

365

警告:使用 Access-Control-Allow-Origin: * 可能会使您的API/网站容易受到跨站请求伪造(CSRF)攻击。在使用此代码之前,务必了解风险

如果您正在使用PHP,那么解决方法非常简单。只需在处理请求的 PHP 页面开头添加以下脚本即可:

<?php header('Access-Control-Allow-Origin: *'); ?>

如果您正在使用 Node-red,则需要在 node-red/settings.js 文件中取消注释以下行以允许 CORS。请注意,保留 HTML 标签。
// The following property can be used to configure cross-origin resource sharing
// in the HTTP nodes.
// See https://github.com/troygoode/node-cors#configuration-options for
// details on its contents. The following is a basic permissive set of options:
httpNodeCors: {
  origin: "*",
  methods: "GET,PUT,POST,DELETE"
},

如果您使用的是与问题相同的Flask,则需要首先安装flask-cors
pip install -U flask-cors

然后在你的应用程序中包含 Flask cors 包。

from flask_cors import CORS

一个简单的应用程序看起来像这样:
from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

@app.route("/")
def helloWorld():
  return "Hello, cross-origin-world!"

如需更多详情,请查看Flask文档


240
因为你不知道CORS的作用,所以不应该关闭它。这会使你的用户处于一种根本上不安全的状态。 - user229044
182
尽管这可能不安全,但问题并不是关于安全性,而是如何完成任务。这是开发者在处理跨域AJAX请求时可以选择的选项之一。它帮助我解决了问题,对我的应用程序来说,我不关心数据来自哪里。我使用PHP在目标域上对所有输入进行了清理,因此,如果有人想往里面发布一些垃圾内容,让他们试试吧。这里的主要观点是,可以允许从目标域发起跨域AJAX请求。赞一个答案。 - ZurabWeb
5
我同意你的观点,我们不应该关闭CORS,但有时在开发应用程序时需要测试应用程序,为此最简单的方法是关闭CORS并检查是否一切正常。许多时候,前端开发人员无法访问可以更改内容的后端系统,或者他们需要编写相应的代理程序。最好的方法是添加一个Chrome扩展程序,以便在开发过程中关闭CORS,这也写在已删除的答案中。 - shruti
1
如果答案(或带有警告的编辑)能够解释在使用php中的header()脚本时,对于谁存在风险,那将非常有帮助。这里的问题是关于一个我们无法控制的外国网站,它只允许我们通过浏览器进行导航和查看,而如果我们需要从我们的服务器访问资源,它会启动CORS保护(以防止我们每秒进行过多的查询)。 因此,我的问题仍然存在,如果在我们的服务器上使用该header()脚本,我们访问者会面临什么危险?编辑是否将访客(我们)与主机混淆了? - Eve
1
@ShadyMohamedSherif,历史上有这样一个时期,人们被允许使用任何方式(浏览器、应用程序等)通过IP/特定端口/访问网站,并且在滥用发生后,网站所有者开始限制与其页面的任何连接仅限于浏览器。换句话说,当人们只是缓慢地请求网页(在浏览器上)时。我的意思是,即使是DDoS攻击也无法通过浏览器进行,更不用说其他可能存在的查询网站内容的阴险方式了。 - Eve
显示剩余4条评论

106

因为$.ajax({type: "POST"调用的是OPTIONS,而$.post( 调用的是POST。二者是不同的。Postman 可以正确地调用“POST”,但我们调用时会变成“OPTIONS”。

对于 C# web 服务——Web API

请在您的 web.config 文件的 <system.webServer> 标签下添加以下代码。这样就可以正常工作:

<httpProtocol>
    <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
    </customHeaders>
</httpProtocol>
请确保在Ajax调用中没有任何错误。

jQuery

$.ajax({
    url: 'http://mysite.microsoft.sample.xyz.com/api/mycall',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
    },
    type: "POST", /* or type:"GET" or type:"PUT" */
    dataType: "json",
    data: {
    },
    success: function (result) {
        console.log(result);
    },
    error: function () {
        console.log("error");
    }
});

注意: 如果你想从第三方网站下载内容,那么这并不能帮助你。你可以尝试以下代码,但不是JavaScript。

System.Net.WebClient wc = new System.Net.WebClient();
string str = wc.DownloadString("http://mysite.microsoft.sample.xyz.com/api/mycall");

80

深层

在下面的调查中,我使用http://example.com作为API地址,而不是你提出的http://myApiUrl/login,因为前者可用。我假设你的页面位于http://my-site.local:8088

注意:API和你的页面拥有不同的域名!

你看到不同的结果的原因是Postman:

  • 设置头部Host = example.com(你的API)
  • 未设置头部Origin
  • Postman实际上根本不使用你的网站URL(你只是在Postman中键入API地址),他只发送请求到API,所以他认为网站的地址与API相同(浏览器不会这么认为)

这类似于当站点和API具有相同域时浏览器发送请求的方式(浏览器也设置了头部项Referer=http://my-site.local: 8088 ,但我在Postman中没有看到它)。 Origin 头部未设置时,通常服务器默认允许此类请求。

输入图片描述

这是Postman发送请求的标准方式。但是,当你的站点和API具有不同的域名时,浏览器会以不同的方式发送请求,然后CORS出现,并自动:

  • 设置头部Host=example.com(为API)
  • 设置头部Origin=http://my-site.local:8088(为你的站点)

(头部Referer的值与Origin相同)。现在在Chrome的控制台和网络选项卡中,你将看到:

输入图片描述

输入图片描述

当你的Host!= Origin时,这是CORS,当服务器检测到这样的请求时,通常会默认阻止它

Origin=null表示在从本地目录打开HTML内容并发送请求时设置的值。当您在<iframe>内发送请求时,也会出现相同的情况,就像下面的代码片段中一样(但此处根本未设置Host标头) - 通常,在HTML规范中说不透明来源的任何地方,您都可以将其翻译为Origin=null。有关更多信息,请访问此处

fetch('http://example.com/api', {method: 'POST'});
Look on chrome-console > network tab

如果您不使用简单的CORS请求,通常浏览器在发送主要请求之前会自动发送OPTIONS请求 - 更多信息请参见这里。下面的片段展示了它:

fetch('http://example.com/api', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json'}
});
Look in chrome-console -> network tab to 'api' request.
This is the OPTIONS request (the server does not allow sending a POST request)

您可以更改服务器配置以允许CORS请求。

这是一个示例配置,用于在nginx上启用CORS(nginx.conf文件) - 在设置nginx的 always / "$http_origin"和Apache的" * "时要非常小心 - 这将解除对任何域的CORS阻止(在生产中,使用消耗API的具体页面地址而不是星号)。

location ~ ^/index\.php(/|$) {
   ...
    add_header 'Access-Control-Allow-Origin' "$http_origin" always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;
    if ($request_method = OPTIONS) {
        add_header 'Access-Control-Allow-Origin' "$http_origin"; # DO NOT remove THIS LINES (doubled with outside 'if' above)
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Max-Age' 1728000; # cache preflight value for 20 days
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'My-First-Header,My-Second-Header,Authorization,Content-Type,Accept,Origin';
        add_header 'Content-Length' 0;
        add_header 'Content-Type' 'text/plain charset=UTF-8';
        return 204;
    }
}

以下是一个示例配置,可以在 Apache 上启用 CORS(.htaccess 文件)

# ------------------------------------------------------------------------------
# | Cross-domain Ajax requests                                                 |
# ------------------------------------------------------------------------------

# Enable cross-origin Ajax requests.
# http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
# http://enable-cors.org/

# <IfModule mod_headers.c>
#    Header set Access-Control-Allow-Origin "*"
# </IfModule>

# Header set Header set Access-Control-Allow-Origin "*"
# Header always set Access-Control-Allow-Credentials "true"

Access-Control-Allow-Origin "http://your-page.com:80"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT"
Header always set Access-Control-Allow-Headers "My-First-Header,My-Second-Header,Authorization, content-type, csrf-token"


10
非常易懂的解释,容易理解!谢谢您! - Nam G VU
1
这个回答清楚地展示了这条评论所描述的内容:如果你在使用Postman时没有看到同样的问题,那么这意味着你不知不觉中没有通过Postman发送相同的请求。干得好! - Ray Jasson
这个答案似乎有些误导性。虽然服务器可能会阻止“Host”和“Origin”不同的请求,但也可能不会。但只要它没有明确发送一个头部告诉它可以完成请求,浏览器将会阻止它以保护用户。这种保护在Postman中不存在,因为它有不同的目的和条件。 - undefined

45

应用CORS限制是由服务器定义并由浏览器实现的安全功能。

浏览器查看服务器的CORS策略并遵守它。

然而,Postman工具不会考虑服务器的CORS策略。

这就是为什么浏览器会出现CORS错误,但在Postman中不会出现。


39
您得到的错误是由CORS标准引起的,该标准对JavaScript执行ajax请求的方式设置了一些限制。
CORS标准是客户端标准,在浏览器中实现。因此,阻止调用完成并生成错误消息的是浏览器,而不是服务器。
Postman不实现CORS限制,这就是为什么您在从Postman进行相同调用时看不到相同错误的原因。
为什么Postman不实现CORS?CORS相对于发起请求的页面的来源(URL域)定义了限制。但在Postman中,请求不是来自具有URL的页面,因此不适用CORS。

8
接受的回答没有解释为什么Postman中的请求成功,而这才是最初的问题所在。 - JacquesB
服务器最初是用来向客户端(浏览器软件程序)发送流,而不是发送到各种桌面或服务器应用程序中,这些应用程序可能会表现出扭曲的方式。浏览器与服务器建立握手协议,接收有关连接的确认,然后数据流恢复。在某些(DDOS)情况下,机器人农场服务器发送了数百万个查询,主机为每个这些停滞的连接分配了许多资源(打开进程),最终从未发生 - 因此阻止了其回答其他合法请求的能力。 - Eve
@Eve:CORS是在浏览器中实施的客户端措施,因此不能防止来自机器人农场的DDOS攻击。 - JacquesB
2
这实际上是最好的解释,让人恍然大悟CORS是一个浏览器标准! - Aris

17

解决方案和问题起源

您正在向不同的域名发出XMLHttpRequest请求,例如:

  1. 域名一:some-domain.com
  2. 域名二:some-different-domain.com

这种域名差异会触发CORS (跨源资源共享) 策略,称为SOP (同源策略),强制在Ajax、XMLHttpRequest和其他HTTP请求中使用相同的域名(因此称为Origin)。

为什么我通过Chrome扩展程序Postman发送请求时可以工作?

客户端(大多数浏览器开发工具)可以选择执行同源策略。

大多数浏览器执行同源策略以防止与CSRF (跨站点请求伪造) 攻击相关的问题。

作为开发工具,Postman选择不执行同源策略(SOP),而某些浏览器则会执行。这就是为什么你可以通过Postman发送请求,而在使用浏览器的JavaScript和XMLHttpRequest时无法发送该请求的原因。请注意保留HTML标记。

7

用于浏览器测试的目的:

Windows - 运行:

chrome.exe --user-data-dir="C://Chrome dev session" --disable-web-security

上述命令将禁用Chrome Web安全功能,因此例如您在本地项目中工作并在尝试进行请求时遇到CORS策略问题,可以使用上述命令跳过此类型的错误。基本上它将打开一个新的Chrome会话。


1
请您能否解释一下? - ZebraCoder
3
上述命令将禁用Chrome网络安全。例如,如果您在本地项目上工作并且在尝试发出请求时遇到CORS策略问题,您可以使用上述命令跳过此类错误。基本上它会打开一个新的Chrome会话。 - Daniel Iftimie
你能否将信息添加到答案本身中?评论可能随时被删除。(但是请不要使用“编辑:”,“更新:”或类似的字眼——答案应该看起来像是今天写的) - Peter Mortensen

6
如果您访问的资源处理时间超过了网关超时时间,您也可能会遇到此错误。对于复杂的数据库查询等情况,可能会出现这种情况。因此,上述错误代码可能掩盖了这个问题。只需检查错误代码是否为504,而不是像Kamil's answer中那样的404或其他内容。如果是504,则增加网关超时时间可能会解决问题。
在我的情况下,可以通过在Internet Explorer浏览器中禁用同源策略(CORS)来消除CORS错误,请参见How to disable same origin policy Internet Explorer。这样做后,在日志中只剩下一个纯粹的504错误。

如果您收到超时错误,则不会收到CORS错误。 - Mr Jedi
我在解决一个系统问题时遇到了CORS错误,这让我感到困惑,后来发现只是超时时间太短导致连接关闭。增加超时时间后,系统表现完美。因此,超时时间引起了“无法访问控制允许来源”错误,这也是我首先进入该线程的原因。所以这可能对其他遇到504错误和此错误的人有所帮助。 - NDM
这意味着您的应用配置可能出了问题。在超时情况下,您不应该收到此错误。 - Mr Jedi
1
我也遇到了一个令人困惑的CORS 504错误,当nginx超时时,我的情况下。增加超时时间使服务恢复正常,没有CORS错误。感谢提示。 - efru

4
为了解决这个问题,请在你所使用的后端代码中的doGet()doPost()函数中写入以下代码: response.setHeader("Access-Control-Allow-Origin", "*");"*"的位置,你可以输入访问该网站的网站或API URL终端,否则它将是公共的。

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