为什么要发送OPTIONS请求,我可以禁用它吗?


415

我正在构建一个Web API。我发现每当我使用Chrome进行POST,GET到我的API时,总是在真实请求之前发送一个OPTIONS请求,这很烦人。目前,我让服务器忽略任何OPTIONS请求。现在,我的问题是,发送一个OPTIONS请求以使服务器的负载增加一倍有什么好处?有什么方法可以完全阻止浏览器发送OPTIONS请求?

Answers:


376

编辑2018-09-13:在此预检请求以及在此响应结束时如何避免它方面增加了一些精度。

OPTIONS请求就是我们在中所称的pre-flight请求Cross-origin resource sharing (CORS)

当您在特定情况下跨不同来源发出请求时,它们是必需的。

某些浏览器会发出此飞行前请求,作为一种安全措施,以确保服务器信任正在执行的请求。意味着服务器了解请求上发送的方法,源和标头是安全的。

每当您尝试进行跨源请求时,服务器都不应忽略,而应处理这些请求。

一个很好的资源可以在这里找到http://enable-cors.org/

处理这些问题的一种方法是确保对于OPTIONS方法的任何路径,服务器使用此标头发送响应

Access-Control-Allow-Origin: *

这将告诉浏览器服务器愿意回答任何来源的请求。

有关如何向服务器添加CORS支持的更多信息,请参见以下流程图

http://www.html5rocks.com/static/images/cors_server_flowchart.png

CORS流程图


编辑2018-09-13

OPTIONS仅在某些情况下触发CORS 请求,如MDN docs中所述

有些请求不会触发CORS预检。尽管Fetch规范(定义了CORS)未使用该术语,但在本文中将其称为“简单请求”。不会触发CORS预检的请求(所谓的“简单请求”)是满足以下所有条件的请求:

唯一允许的方法是:

  • 得到
  • 开机自检

除了由用户代理自动设置的标头(例如,Connection,User-Agent或在Fetch规范中定义为“禁止标头名”的任何其他标头)外,仅允许将标头手动设置的是Fetch规范定义为“ CORS安全列出的请求标头”的设置,它们是:

  • 接受
  • 接受语言
  • 内容语言
  • 内容类型(但请注意以下其他要求)
  • DPR
  • 下行链接
  • 保存数据
  • 视口宽度
  • 宽度

Content-Type标头的唯一允许值为:

  • 应用程序/ x-www-form-urlencoded
  • 多部分/表单数据
  • 文字/纯文字

没有在请求中使用的任何XMLHttpRequestUpload对象上注册事件侦听器;这些可以使用XMLHttpRequest.upload属性访问。

请求中未使用ReadableStream对象。


8
但是将Chrome标志设置给所有普通用户是不现实的。
钱琛

37
说在进行跨源请求时需要预检请求是不正确的。仅在特定情况下才需要进行预检请求,例如,如果要设置自定义标题,或者发出除get,head和post之外的其他请求。
罗宾·克洛尔斯

4
有趣的是,当使用jQuery发出CORS请求时,JavaScript库特别避免设置自定义标头,并警告开发人员:对于跨域请求,我们认为预检条件类似于拼图游戏,我们根本就不用确定。
低于雷达

3
如果我curl对它起作用的api 怎么做,但是从chrome运行时出现错误?
SuperUberDuper

5
@SuperUberDuper,因为CORS和预检请求与浏览器有关。您可以通过Origin在请求中添加标头来模拟CORS,以模拟该请求,就像请求来自特定主机(例如yourwebsite.com)一样。您也可以通过设置到请求的HTTP方法模拟预检要求OPTIONSAccess-Control-*
利奥·科雷亚

234

经历了这个问题之后,下面是我对该问题的总结和解决方案。

根据CORS策略(强烈建议您阅读它),您不能仅仅强迫浏览器认为有必要就停止发送OPTIONS请求。

有两种解决方法:

  1. 确保您的请求是“简单请求”
  2. Access-Control-Max-Age为OPTIONS请求设置

简单的要求

一个简单的跨站点请求是满足以下所有条件的请求:

唯一允许的方法是:

  • 得到
  • 开机自检

除了由用户代理自动设置的标头(例如,Connection,User-Agent等)之外,唯一允许手动设置的标头是:

  • 接受
  • 接受语言
  • 内容语言
  • 内容类型

Content-Type标头的唯一允许值为:

  • 应用程序/ x-www-form-urlencoded
  • 多部分/表单数据
  • 文字/纯文字

一个简单的请求不会导致飞行前OPTIONS请求。

为选项检查设置缓存

您可以Access-Control-Max-Age为OPTIONS请求设置一个,以便它在到期之前不会再次检查该权限。

Access-Control-Max-Age提供以秒为单位的值,该值表示可以在不发送其他预检请求的情况下将对预检请求的响应进行缓存的时间。

注意的限制

  • 对于铬,最大秒Access-Control-Max-Age就是600其为10分钟,根据铬源代码
  • Access-Control-Max-Age每次仅对一种资源起作用,例如,GET具有相同URL路径但不同查询的请求将被视为不同资源。因此,对第二个资源的请求仍将触发预检请求。

3
是的...这应该是公认的答案,并且与问题最相关。
Rajesh Mbm

7
谢谢您的提及Access-Control-Max-Age。这就是关键。它可以帮助您避免过多的预检请求。
Idris Mokhtarzada

我正在使用axios调用获取请求。我在哪里可以在axios请求中设置Access-Control-Max-Age?
Mohit Shah

+1 Access-Control-Max-Age标头是此处的关键。这应该是公认的答案!我在标头上设置了86400秒(24小时),偏好设置请求消失了!
revobtz

1
@VitalyZdanevich不!不要application/json仅仅因为它使您的请求变得“简单”(从而触发CORS)就回避。浏览器正在执行其工作。将您的服务器设置为返回标头之类的内容Access-Control-Max-Age: 86400,浏览器将在24小时内不重新发送OPTIONS请求。
colm.anseo,

139

请根据实际的预检选项请求参考此答案:CORS-引入预检请求的动机是什么?

要禁用OPTIONS请求,必须满足ajax请求的以下条件:

  1. 请求未设置自定义HTTP标头,例如“ application / xml”或“ application / json”等
  2. request方法必须是GET,HEAD或POST之一。如果POST,内容类型应该是一个application/x-www-form-urlencodedmultipart/form-datatext/plain

参考:https : //developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS


14
为“自定义HTTP标头” +1!以我为例,它们导致触发飞行前请求。当请求正文和OPTIONS请求停止发送时,我重构了请求以发送我在标头中发送的任何内容。
安德烈

21
application/xmlapplication/json不是“自定义HTTP标头”。标头本身就是这样,Content-Type并且将标头称为“自定义”将产生误导。
Leo Correa

1
删除了自定义HTTP标头,这就像一个魅力!
蒂姆D

47

打开调试控制台并打开该Disable Cache选项后,将始终发送预检请求(即,在每个请求之前)。如果您不禁用缓存,则预检请求将仅发送一次(每台服务器)


3
哦,我在想什么。调试了几个小时,这是我的解决方案。缓存由于调试控制台而被禁用。
毛里求斯2013年

1
即使调试控制台关闭,也会发送预检请求
Luca Perico

2
Luca:的确如此,但要点是,关闭开发工具时,“禁用缓存”无效。如果未禁用缓存,则预检请求仅发送一次(当然,每个服务器)(如果禁用了缓存,则在每个请求之前发送)。
尼尔

那真的很有帮助。
阿努拉格·帕蒂尔

38

是的,可以避免选择要求。当您将任何数据发送(发布)到另一个域时,选项请求是预检请求。这是浏览器的安全问题。但是我们可以使用另一种技术:iframe传输层。我强烈建议您忘记任何CORS配置并使用现成的解决方案,它可以在任何地方使用。

在这里看看:https : //github.com/jpillora/xdomain

工作示例:http : //jpillora.com/xdomain/


这实际上是一种嵌入式代理吗?
matanster'7

15
“选项请求是将任何数据发送(发布)到另一个域时的预检请求。” - 这不是真的。您可以使用XHR发送可以使用普通HTML格式发送的任何POST请求,而无需触发预检请求。仅当您开始执行表单无法完成的操作(例如自定义内容类型或额外的请求标头)时,才发送预检。
昆汀

这里的解决方案似乎依赖于iframe填充程序,该填充程序在某些情况下可以工作,但有一些主要限制。如果您想知道响应的HTTP状态代码或另一个HTTP响应标头的值,会发生什么?
史蒂芬·克罗斯比

5
iFrame并非为此而设计。
罗姆科

1
这是一个软件实现,并且确实回答了最后一个问题:“是否有任何方法可以完全阻止浏览器发送OPTIONS请求?”。长话短说,在Mozilla或Chromium中无法禁用它,它已在代码中实现,唯一的“工作”选项只是在规避此行为。
清道夫

15

对于了解其存在原因但需要访问未经身份验证即无法处理OPTIONS调用的API的开发人员,我需要一个临时答复,以便可以在本地进行开发,直到API所有者添加适当的SPA CORS支持或获得代理API启动并运行。

我发现您可以在Mac的Safari和Chrome中禁用CORS。

在Chrome中禁用同一来源政策

Chrome:退出Chrome,打开终端并粘贴以下命令: open /Applications/Google\ Chrome.app --args --disable-web-security --user-data-dir

Safari:在Safari中禁用同源策略

如果要在Safari上禁用同源策略(我有9.1.1),则只需启用开发人员菜单,然后从开发菜单中选择“禁用跨域限制”。


4
您应该在标有“这绝不是永久解决方案!!!”的部分上加亮点。。相同来源策略是一种非常重要的浏览器安全措施,在正常浏览Internet时切勿禁用该策略。
jannis'9

我希望网络能够以这种方式工作,因此我们可以轻松地从服务器请求所需的数据。
jemiloii

14

如先前的帖子所述,OPTIONS请求的存在是有原因的。如果您对服务器的响应时间过长(例如,海外连接)有疑问,也可以让浏览器缓存预检请求。

让您的服务器回复Access-Control-Max-Age标头,对于转到相同端点的请求,预检请求将被缓存并且不再发生。


1
这次真是万分感谢!OPTIONS在我阅读的所有CORS文档中,都将使用此标头缓存请求的事实是非常不透明的。
joshperry

并且缓存仅在完全相同的网址下生效。我想要一个域级别的预检缓存,它可以真正减少往返次数。(CORS很愚蠢!)
想知道

8

我已经解决了这个问题。

if($_SERVER['REQUEST_METHOD'] == 'OPTIONS' && ENV == 'devel') {
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Headers: X-Requested-With');
    header("HTTP/1.1 200 OK");
    die();
}

它仅用于发展。与此相关,我正在等待9ms和500ms,而不是8s和500ms。我之所以能够这样做,是因为生产JS应用程序将与生产设备位于同一台机器上,所以将没有任何其他东西,OPTIONS但是开发是我的本地工作。


4

您不能,但是可以避免使用JSONP的CORS。


2
如果您做的事情不简单,您只会收到OPTIONS请求。您只能使用JSONP发出简单的请求(GET,无自定义标头,无身份验证数据),因此JSONP不能替代此处。
昆丁

是的,我知道,但是我不确切地知道项目要求。我知道这不是简单的避免cors,但这取决于项目。在最坏的情况下,为了避免CORS,您需要使用get参数传递数据。因此,JSONP可以根据项目要求(如您所说,使用简单的请求)替换cors
Jose Mato

我不了解所谓的飞行前设计。如果客户端决定将数据发送到服务器,那会导致它不安全吗?我认为将电线上的负载增加一倍没有任何意义。
钱琛

@ElgsQianChen这可能可以回答您的问题stackoverflow.com/questions/15381105/…–
Leo Correa

0

花了整整一天半的时间来解决类似的问题后,我发现它与IIS有关

我的Web API项目的设置如下:

// WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
    var cors = new EnableCorsAttribute("*", "*", "*");
    config.EnableCors(cors);
    //...
}

在web.config> system.webServer节点中没有CORS特定的配置选项,就像我在很多帖子中看到的那样

global.asax或控制器中的装饰器中没有CORS特定代码

问题是应用程序池设置

托管管道模式设置为经典(把它改成集成)和标识设置为网络服务(将其改为ApplicationPoolIdentity

更改这些设置(并刷新应用程序池)对我来说已经解决了。


-2

对我有用的是导入“ github.com/gorilla/handlers”,然后以这种方式使用它:

router := mux.NewRouter()
router.HandleFunc("/config", getConfig).Methods("GET")
router.HandleFunc("/config/emcServer", createEmcServers).Methods("POST")

headersOk := handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type"})
originsOk := handlers.AllowedOrigins([]string{"*"})
methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"})

log.Fatal(http.ListenAndServe(":" + webServicePort, handlers.CORS(originsOk, headersOk, methodsOk)(router)))

一旦我执行了Ajax POST请求并将JSON数据附加到该请求,Chrome就会始终添加Content-Type标头,该标头不在我以前的AllowedHeaders配置中。


-2

我过去使用的一种解决方案-假设您的网站位于mydomain.com上,并且您需要向foreigndomain.com发出ajax请求

配置从您的域到外部域的IIS重写-例如

<rewrite>
  <rules>
    <rule name="ForeignRewrite" stopProcessing="true">
        <match url="^api/v1/(.*)$" />
        <action type="Rewrite" url="https://foreigndomain.com/{R:1}" />
    </rule>
  </rules>
</rewrite>

在mydomain.com网站上-然后您可以发出相同的原始请求,而无需任何选项请求:)


-2

如果使用代理来拦截请求并写入适当的标头,则可以解决该问题。在Varnish的特定情况下,这些是规则:

if (req.http.host == "CUSTOM_URL" ) {
set resp.http.Access-Control-Allow-Origin = "*";
if (req.method == "OPTIONS") {
   set resp.http.Access-Control-Max-Age = "1728000";
   set resp.http.Access-Control-Allow-Methods = "GET, POST, PUT, DELETE, PATCH, OPTIONS";
   set resp.http.Access-Control-Allow-Headers = "Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since";
   set resp.http.Content-Length = "0";
   set resp.http.Content-Type = "text/plain charset=UTF-8";
   set resp.status = 204;
}

}


-5

可能有一个解决方案(但我没有测试过):您可以使用CSP(内容安全策略)来启用您的远程域,浏览器可能会跳过CORS OPTIONS请求验证。

如果有时间,我将对其进行测试并更新此帖子!

CSP:https//developer.mozilla.org/fr/docs/Web/HTTP/Headers/Content-Security-Policy

CSP规范:https//www.w3.org/TR/CSP/


我刚刚测试过,但它不起作用,xhr请求允许CSP后仍需要CORS ...
Arnaud Tournier18年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.