40
原文:What you should know about CORS
作者:Nicolas Bailly
译者:博轩

图片描述

如果你和我一样,第一次遇到 CORS (跨域资源共享),你想让服务器接收那些你拼接的 Ajax 请求并处理他们。所以你去 stackoverflow.com 复制一段代码来设置一些 HTTP Headers,让请求可以正常工作。

但是,可能还有一些事情你应该知道。

CORS 是什么,不是什么

新手通常混淆的原因,就是因为他们并不明确 CORS 能做什么。首先,CORS 并不是一种安全措施,实际上恰恰相反:CORS 是一种绕过“同源策略(SOP)”的方法。同源策略是一种安全措施,阻止您向其他域发出Ajax请求。

同源策略声明一个域上的网站,无法向另一个域发出 XMLHttpRequest(XHR) 请求。这可以防止恶意网站向已知网站(比如 Facebook 或者 Google)发出请求,改变用户的登录状态,以便可以冒充其他用户。此策略由浏览器实现(所有浏览器都实现了同源策略,尽管实现细节上存在细微的差别),这意味着此策略并不适用于从服务器,或者任何其他HTTP客户端(比如 cURLpostman)发出的请求。此外,服务器同样无法完全控制它:服务器将处理每个请求,并假设他们都来自可信域,请求是否会被阻止完全取决于浏览器。

同源策略绝不意味着防止攻击者向您的服务器发出请求(因为攻击者显然不会使用浏览器)。它只是为了防止合法用户在使用知名浏览器浏览网站时,在不知情的情况下,向你的网站发起请求。

CORS 是一种绕过同源策略的方法,在某些情况下,您希望一些特殊的站点可以向你的服务器发起请求,即使正常情况下它会被阻止。(通常,是允许您的前端应用向您的API发出请求)。

CORS 是如何工作的

HTTP的相同,CORS基本上也是浏览器和服务器之间的对话。假设你前端的域名为 domain-a.com ,后端API的域名为 domain-b.com,对话会是这样的:

  • 浏览器:“Hey domain-b.comdomain-a.com上的这个脚本要向你发起一次Ajax请求,但是我应该阻止它,除非你告诉我这个请求是没问题的。”
  • 服务器:“我不知道,但是我可以告诉你,https://domain-a.com 只允许发送 GET,POST,OPTIONS 和 DELETE 请求,并且需要每10分钟验证一次。”
浏览器想了想:“ yeah,这是个正确的域名,我应该给他发送请求。”
  • 浏览器:“Hey domain-b.com,我想在这个终端向你发送 POST 请求。”
  • 服务器:“没问题,这是你的 200

或者,如果用户位于不同的域,则对话会更短:

  • 浏览器:“Hey domain-b.commalicious-domain.com(恶意站点)上的这个脚本要向你发起一次Ajax请求,但是我应该阻止它,除非你告诉我这个请求是没问题的。”
  • 服务器:“我不知道,但是我可以告诉你,https://domain-a.com 只允许发送 GET,POST,OPTIONS 和 DELETE 请求,并且需要每10分钟验证一次。”
浏览器想了想:“ Oh,这不是正确的域名,我们最好不要发送请求”,然后在控制台打印了错误。
译注:第二种,使用开发者工具查看时,看不到 Response Headers,但是可以看到下图中的提示:

图片描述

Node CORS 测试地址

在浏览器中的样子

在上面的小场景中,浏览器提出的第一个问题称为 预检请求,对应的 HTTP 谓词是 OPTIONS。遇到这种预检请求,服务器应该总是返回一个 200 的响应,没有正文,但是会包含 Access-Control-Allow-Origin ,以及一些其他响应头。在我们的示例中响应头如下:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://domain-a.com
Access-Control-Allow-Methods: GET, POST, OPTIONS, DELETE
Access-Control-Max-Age: 3600

它告诉浏览器,它只能响应来自 domain-a.com 的请求,可以处理 GET, POST, OPTIONS 或者 DELETE 请求(PUT 请求会被阻止),并且他可以缓存此信息 3600 秒,所以它不需要都发起一个新的 OPTIONS 请求。

图片描述

当然,如果我们使用其他域名,这将不起作用。浏览器会发送 OPTIONS 请求,然后在控制台中抛出异常,并且永远不会发送 POST 请求。

图片描述

很直接,对吧?

但是,也存在一些陷阱...

关于 CORS 的棘手问题

所求请求都包含 CORS 头(headers

您可能会认为,如果您的服务器响应 OPTIONS 请求时返回 200,然后你将这些正确的响应头去掉。然后你将看到浏览器先发送了 OPTIONS 请求,然后发送了其他请求,其他请求挂掉了... 这是因为每个请求(GET, POST, 或者其他请求)都应该包含相同的响应头:“Access-Control-Allow-Headers”。

并非所有请求都会触发预检请求

有一些请求不会触发预检请求,比如 GET 请求,或者 Content-Type 设置为 application/x-www-form-urlencoded 的 POST 请求。这些是浏览器一直允许的“简单请求”,(即使在CORS不支持的情况下,你依然可以使用超链接(a标签)或者使用 POST 请求向其他网站提供表单,您可以在此处找到完整列表。在 POST 请求的情况下,结果会有些违反直觉:浏览器将发出 POST 请求(因此您的服务器可能会保留一些数据),然后忽略响应。

在传统的Web应用程序中,您可以使用 application/json 作为 content-type,因此会有预检请求,但请记住,您的服务器可能仍会收到来自其他域的 POST 请求,因此请不要盲目接受它们。

被允许的域名必须包含协议

您不能只将 mydomain.com 当做域名使用,它还需要包含协议,(例如:https://mydomain.com)。有趣的是,你不能同时接收 httphttps ,因为......

您只能允许一个域

您可以使用 Access-Control-Allow-Origin: * 来允许每个域访问,也可以只允许一个域访问。这意味着如果您需要多个域来访问您的API时,您需要自己处理它。

处理此问题的最简单方法是在服务器上维护允许访问的域列表,如果域位于该列表中,则动态的改变响应头的内容。下面是一个 PHP 的例子:

$allowedDomains = [
    "http://www.mydomain.com",
    "https://www.mydomain.com",
    "http://www.myotherdomain.com",
    "http://www.myotherdomain.com",
];

$originDomain = $_SERVER['HTTP_ORIGIN'];

if (in_array($originDomain, $allowedDomains)) {
    header("Access-Control-Allow-Origin: $originDomain");
};

或者 Node.js 的例子(改编自这个 stackoverflow 答案

app.use(function(req, res, next) {
  const allowedOrigins = [
    "http://www.mydomain.com",
    "https://www.mydomain.com",
    "http://www.myotherdomain.com",
    "http://www.myotherdomain.com",
  ];
  const origin = req.headers.origin;
  if(allowedOrigins.indexOf(origin) > -1){
    res.setHeader('Access-Control-Allow-Origin', origin);
  }
  return next();
});
同源策略适用于 Chrome 和 Safari 的文件系统,不适用于 Firefox 的

如果您向本地文件发出请求,Firefox会认为它始终位于同一个域上并允许该请求。基于 Webkit 的浏览器(如Chrome或Safari)会将此视为安全风险,并阻止对本地文件的Ajax查询。解决这个问题的唯一方法是使用Firefox,或安装将发送 Access-Control-Allow-Origin: * 响应头的Web服务器。
正如 @brianjenkins94 在评论中指出的那样,您也可以用 --disable-web-security 参数来启动Chrome 。

iOS WKWebview需要CORS

如果您正在开发使用 webview(使用Cordova或Ionic)的移动应用程序,Android将不会给您带来任何麻烦,但iOS上的新 WKWebview 将需要CORS。这意味着您几乎必须始终将 Access-Control-Allow-Origin 标头设置为 * ,但实际上这并不理想。
另一个选择是不在您的应用程序中发出Ajax请求并使用 cordova 插件来生成本机 http 请求,这将很方便的绕过同源策略。

谢谢阅读 !
如果您想要更深入地了解CORS,请访问MDN:
https://developer.mozilla.org...

本文已经联系原文作者,并授权翻译,转载请保留原文链接

joking_zhang
2.5k 声望9.5k 粉丝

"Life's simple , you make choices and you don't look back."