原文: What you should know about CORS
做者:Nicolas Bailly
译者:博轩
若是你和我同样,第一次遇到 CORS (跨域资源共享),你想让服务器接收那些你拼接的 Ajax
请求并处理他们。因此你去 stackoverflow.com 复制一段代码来设置一些 HTTP Headers,让请求能够正常工做。前端
可是,可能还有一些事情你应该知道。node
新手一般混淆的缘由,就是由于他们并不明确 CORS 能作什么。首先,CORS 并非一种安全措施,实际上偏偏相反:CORS 是一种绕过“同源策略(SOP)”的方法。同源策略是一种安全措施,阻止您向其余域发出Ajax
请求。web
同源策略声明一个域上的网站,没法向另外一个域发出 XMLHttpRequest(XHR) 请求。这能够防止恶意网站向已知网站(好比 Facebook 或者 Google)发出请求,改变用户的登陆状态,以即可以冒充其余用户。此策略由浏览器实现(全部浏览器都实现了同源策略,尽管实现细节上存在细微的差异),这意味着此策略并不适用于从服务器,或者任何其余HTTP客户端(好比 cURL ,postman)发出的请求。此外,服务器一样没法彻底控制它:服务器将处理每一个请求,并假设他们都来自可信域,请求是否会被阻止彻底取决于浏览器。json
同源策略毫不意味着防止攻击者向您的服务器发出请求(由于攻击者显然不会使用浏览器)。它只是为了防止合法用户在使用知名浏览器浏览网站时,在不知情的状况下,向你的网站发起请求。后端
CORS 是一种绕过同源策略的方法,在某些状况下,您但愿一些特殊的站点能够向你的服务器发起请求,即便正常状况下它会被阻止。(一般,是容许您的前端应用向您的API发出请求)。跨域
与HTTP的相同,CORS基本上也是浏览器和服务器之间的对话。假设你前端的域名为 domain-a.com
,后端API的域名为 domain-b.com
,对话会是这样的:浏览器
domain-b.com
,domain-a.com
上的这个脚本要向你发起一次Ajax
请求,可是我应该阻止它,除非你告诉我这个请求是没问题的。”https://domain-a.com
只容许发送 GET,POST,OPTIONS 和 DELETE 请求,而且须要每10分钟验证一次。”浏览器想了想:“ yeah,这是个正确的域名,我应该给他发送请求。”
domain-b.com
,我想在这个终端向你发送 POST 请求。”200
”或者,若是用户位于不一样的域,则对话会更短:缓存
domain-b.com
,malicious-domain.com
(恶意站点)上的这个脚本要向你发起一次Ajax
请求,可是我应该阻止它,除非你告诉我这个请求是没问题的。”https://domain-a.com
只容许发送 GET,POST,OPTIONS 和 DELETE 请求,而且须要每10分钟验证一次。”浏览器想了想:“ Oh,这不是正确的域名,咱们最好不要发送请求”,而后在控制台打印了错误。
译注:第二种,使用开发者工具查看时,看不到 Response Headers,可是能够看到下图中的提示:
在上面的小场景中,浏览器提出的第一个问题称为 预检请求,对应的 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
请求。
很直接,对吧?
可是,也存在一些陷阱...
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
)。有趣的是,你不能同时接收 http
和 https
,由于......
您可使用 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(); });
若是您向本地文件发出请求,Firefox会认为它始终位于同一个域上并容许该请求。基于 Webkit 的浏览器(如Chrome或Safari)会将此视为安全风险,并阻止对本地文件的Ajax
查询。解决这个问题的惟一方法是使用Firefox,或安装将发送 Access-Control-Allow-Origin: *
响应头的Web服务器。
正如 @brianjenkins94 在评论中指出的那样,您也能够用 --disable-web-security
参数来启动Chrome 。
若是您正在开发使用 webview
(使用Cordova或Ionic)的移动应用程序,Android将不会给您带来任何麻烦,但iOS上的新 WKWebview
将须要CORS。这意味着您几乎必须始终将 Access-Control-Allow-Origin
标头设置为 *
,但实际上这并不理想。
另外一个选择是不在您的应用程序中发出Ajax
请求并使用 cordova
插件来生成本机 http
请求,这将很方便的绕过同源策略。
谢谢阅读 !
若是您想要更深刻地了解CORS,请访问MDN:
https://developer.mozilla.org...
本文已经联系原文做者,并受权翻译,转载请保留原文连接