我正在构建一个 Web API。我发现每当我使用 Chrome 发布、获取到我的 API 时,总是在真正的请求之前发送一个 OPTIONS 请求,这很烦人。目前,我让服务器忽略任何 OPTIONS 请求。现在我的问题是发送 OPTIONS 请求以使服务器负载加倍有什么好处?有什么方法可以完全阻止浏览器发送 OPTIONS 请求?
15 回答
编辑 2018-09-13:添加了一些关于此飞行前请求的精确度以及如何在此响应结束时避免它。
OPTIONS
requests 就是我们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
编辑 2018-09-13
CORSOPTIONS
请求仅在某些情况下触发,如MDN 文档中所述:
有些请求不会触发 CORS 预检。这些在本文中被称为“简单请求”,尽管 Fetch 规范(定义 CORS)没有使用该术语。不触发 CORS 预检的请求(即所谓的“简单请求”)是满足以下所有条件的请求:
唯一允许的方法是:
- 得到
- 头
- 邮政
除了由用户代理自动设置的标头(例如,Connection、User-Agent 或任何其他在 Fetch 规范中定义为“禁止标头名称”的标头)之外,唯一允许的标头是手动设置的是 Fetch 规范定义为“CORS-safelisted request-header”的那些,它们是:
- 接受
- 接受语言
- 内容-语言
- Content-Type(但请注意下面的附加要求)
- 民主共和国
- 下行链路
- 保存数据
- 视口宽度
- 宽度
Content-Type 标头的唯一允许值是:
- 应用程序/x-www-form-urlencoded
- 多部分/表单数据
- 文本/纯文本
请求中使用的任何 XMLHttpRequestUpload 对象上都没有注册事件侦听器;这些是使用 XMLHttpRequest.upload 属性访问的。
请求中没有使用 ReadableStream 对象。
已经经历过这个问题,下面是我对这个问题的结论和我的解决方案。
根据CORS 策略(强烈建议您阅读它),如果它认为需要,您不能只强制浏览器停止发送 OPTIONS 请求。
有两种方法可以解决它:
- 确保您的请求是“简单请求”
Access-Control-Max-Age
为 OPTIONS 请求设置
简单的请求
一个简单的跨站点请求是满足以下所有条件的请求:
唯一允许的方法是:
- 得到
- 头
- 邮政
除了由用户代理自动设置的标头(例如 Connection、User-Agent 等)之外,唯一允许手动设置的标头是:
- 接受
- 接受语言
- 内容-语言
- 内容类型
Content-Type 标头的唯一允许值是:
- 应用程序/x-www-form-urlencoded
- 多部分/表单数据
- 文本/纯文本
一个简单的请求不会引起飞行前的 OPTIONS 请求。
为 OPTIONS 检查设置缓存
您可以Access-Control-Max-Age
为 OPTIONS 请求设置一个,这样它就不会再次检查权限,直到它过期。
Access-Control-Max-Age 以秒为单位给出对预检请求的响应可以缓存多长时间而不发送另一个预检请求的值。
注意到限制
- 对于 Chrome,根据chrome 源代码
Access-Control-Max-Age
,最大秒数为10 分钟600
Access-Control-Max-Age
每次仅对一种资源有效,例如,GET
具有相同 URL 路径但查询不同的请求将被视为不同的资源。所以对第二个资源的请求仍然会触发一个预检请求。
请在实际需要预检 OPTIONS 请求的情况下参考此答案:CORS - 引入预检请求的动机是什么?
要禁用 OPTIONS 请求,ajax 请求必须满足以下条件:
- 请求未设置自定义 HTTP 标头,例如“application/xml”或“application/json”等
- 请求方法必须是 GET、HEAD 或 POST 之一。如果是 POST,则内容类型应为
application/x-www-form-urlencoded
、multipart/form-data
或text/plain
参考: https ://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
当您打开调试控制台并打开该Disable Cache
选项时,将始终发送预检请求(即在每个请求之前)。如果您不禁用缓存,则只会发送一次飞行前请求(每台服务器)
是的,可以避免选项请求。选项请求是您将任何数据发送(发布)到另一个域时的预检请求。这是浏览器安全问题。但我们可以使用另一种技术:iframe 传输层。我强烈建议您忘记任何 CORS 配置并使用现成的解决方案,它可以在任何地方工作。
看看这里: https ://github.com/jpilora/xdomain
和工作示例:http: //jpilora.com/xdomain/
对于了解它存在的原因但需要访问在没有身份验证的情况下不处理 OPTIONS 调用的 API 的开发人员,我需要一个临时答案,以便我可以在本地开发,直到 API 所有者添加适当的 SPA CORS 支持或我获得代理 API启动并运行。
我发现您可以在 Mac 上的 Safari 和 Chrome 中禁用 CORS。
Chrome:退出 Chrome,打开终端并粘贴以下命令:open /Applications/Google\ Chrome.app --args --disable-web-security --user-data-dir
Safari:在 Safari 中禁用同源策略
如果你想在 Safari 上禁用同源策略(我有 9.1.1),那么你只需要启用开发者菜单,然后从开发菜单中选择“禁用跨域限制”。
正如在之前的帖子中已经提到的,OPTIONS
请求的存在是有原因的。如果您的服务器响应时间过长(例如海外连接)有问题,您也可以让浏览器缓存预检请求。
让您的服务器回复Access-Control-Max-Age
标头,对于前往同一端点的请求,预检请求将被缓存并且不再发生。
我已经解决了这个问题。
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();
}
它只是为了发展。有了这个,我正在等待 9 毫秒和 500 毫秒,而不是 8 秒和 500 毫秒。我可以这样做,因为生产 JS 应用程序将与生产在同一台机器上,所以不会有,OPTIONS
但开发是我本地的。
你不能,但你可以避免使用 JSONP 的 CORS。
在花了一整天半的时间试图解决一个类似的问题后,我发现它与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)
更改这些设置(并刷新应用程序池)为我修复了它。
OPTIONS 请求是网络浏览器的一个特性,所以要禁用它并不容易。但我找到了一种使用proxy将其重定向的方法。如果服务端点还不能处理 CORS/OPTIONS、可能仍在开发中或配置错误,这很有用。
脚步:
- 使用选择的工具(nginx,YARP,...)为此类请求设置反向代理
- 创建一个端点来处理 OPTIONS 请求。创建一个普通的空端点可能更容易,并确保它可以很好地处理 CORS。
- 为代理配置两组规则。一种是将所有 OPTIONS 请求路由到上面的虚拟端点。另一个将所有其他请求路由到有问题的实际端点。
- 更新网站以改用代理。
基本上这种方法是欺骗 OPTIONS 请求有效的浏览器。考虑到 CORS 不是为了增强安全性,而是为了放宽同源策略,我希望这个技巧能奏效一段时间。:)
我过去使用过的一种解决方案 - 假设您的网站在 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 网站上 - 然后您可以发出相同的来源请求,并且不需要任何选项请求:)
如果使用代理拦截请求并写入适当的标头,则可以解决此问题。在 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;
}
}
对我有用的是导入“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 总是会添加我之前的 AllowedHeaders 配置中没有的 Content-Type 标头。
也许有一个解决方案(但我没有测试它):您可以使用 CSP(内容安全策略)来启用您的远程域,并且浏览器可能会跳过 CORS OPTIONS 请求验证。
如果有时间,我会测试并更新这篇文章!
CSP:https ://developer.mozilla.org/fr/docs/Web/HTTP/Headers/Content-Security-Policy
CSP 规范:https ://www.w3.org/TR/CSP/