@yangfch3
2017-03-07T22:31:50.000000Z
字数 4030
阅读 3825
FE
同源策略是浏览器采取的安全策略,目的是为了保护用户信息的安全。同源策略的根源是 浏览器对非同域页面的不信任。
哪些是不受同源策略限制的:
提交表单不受同源政策的限制。
同时一般情况下由 DOM 发起的网络请求(例如:img, script 等 DOM 元素加载资源)也是不受同源策略的限制的
之所以说一般情况是因为还存在内容安全策略(Content Security Policy,CSP)这个东东,可以通过 HTTP 响应头的 CSP 字段告知浏览器我当前这个页面允许资源加载的白名单:
随着互联网的发展,"同源政策"越来越严格。目前,如果非同源,受同源策略限制的:
(不同源的窗口或框架)DOM 无法获得。
非同源页面向我们的源发起的 AJAX 请求无法正常接收获得数据。
注意无法接收几个字。
Cookie 不能跨域读写是毋庸置疑的。
如果两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置 document.domain
共享 Cookie(父子框架、窗口)。
另外,服务器也可以在设置 Cookie 的时候,指定 Cookie 的所属域名为一级域名,比如 .example.com
。
Set-Cookie: key=value; domain=.example.com; path=/
这样的话,二级域名和三级域名等 .example.com
的子集域名不用做任何设置,都可以读取这个 Cookie。
见下文
如果两个网页(不同的框架或窗口)不同源,就无法拿到对方的 DOM。典型的例子是 iframe
窗口和 window.open
方法打开的窗口,它们与父窗口无法通信。
为什么要这样呢?
试想以下场景,假如一个钓鱼站点(例如:www.icioud.com
),使用 Safari 嵌入了真正的www.icloud.com
页面,用户在嵌入的框架内输入了账户密码。此时,如果我们可以跨域访问iframe
内的 DOM,那么这个钓鱼页面就拿到了我们的账户和密码。
对此浏览器及相关规范采取了相关策略:
iframe
嵌入(具体实现略)iframe
与 window.open
打开的窗口不能访问其 DOM那么如果:
iframe
与 window.open
打开的窗口中的 DOM那么此时我们需要怎么做呢?
如果两个窗口 一级域名相同,只是二级域名不同,那么设置上一节介绍的 document.domain
属性,就可以规避同源政策,拿到 DOM。
iframe 三层:
a.com
>b.com/data.html
>a.com/proxy.html
b.com/data.html
可以通过 Ajax 轮询或其他手段获取数据,数据被 attach 到内嵌iframe[src=a.com/proxy.html]
src
的hash
上,a.com/proxy.html
监听hashChange
事件/定时检查(旧浏览器下),a.com/proxy.html
与最外层的a.com
同域,所以可以正常通信。
同一窗口或框架加载任何页面 window.name
的值始终保持不变。由于 window.name
这个显著的特点,使其适用于在不同源之间进行跨域通信,但这是个不常用的属性。同时 window.name
可存储的信息达到 2 M,可满足大部分情况下的信息传输。
当页面 A 想要从另一个源获取资源或 Web 服务,首先在自己的页面上创建一个隐藏的 iframe B(当然新开一个窗口本质上相同),将 B 指向外部资源或服务(例如:otherdomain.com/data.php
),B 加载完成之后,将把响应的数据(还是需要 B 所在服务器预先准备好)附加到 window.name 上。由于现在 A 和 B 还不同源,A 依旧不能获取到 B 的 name 属性。当 B 获取到数据之后,再将页面导航(location 变更)到任何一个与 A 同源的页面(例如:domain.com/proxy.html
),这时 A 就可以直接获取到 B 的 name 属性值。
当需要拿取最新的数据时,再变更 B 的 src 到 otherdomain.com/data.php
以更新 window.name
,再将页面导航到任何一个与 A 同源的页面即可再次通过 window.name
拿到更新的数据。
一般隐藏的 iframe B 在服务器端会类似下面:
<?php
echo '<script> window.name = "{\"name\":\"hanzichi\", \"age\":10}"; </script>'
// echo 的 window.name 可能会频繁更新
?>
很容易知晓,这个方案存在一定的缺点:
HTML5 为了解决这个问题(窗口与框架间通信的问题),引入了一个全新的 API:跨文档通信 API(Cross-document messaging, CDM)。
这个 API 为 window 对象新增了一个 window.postMessage
方法,允许跨窗口通信,不论这两个窗口是否同源。
举例来说,父窗口 http://aaa.com
向子窗口 http://bbb.com
发消息,调用 postMessage
方法就可以了。
var popup = window.open('http://bbb.com', 'title');
popup.postMessage('Hello World!', 'http://bbb.com');
postMessage
方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为 *,表示不限制域名,允许向所有窗口发送。
子窗口向父窗口发送消息的写法类似。
window.opener.postMessage('Nice to see you', 'http://aaa.com');
父窗口和子窗口都可以通过message事件,监听对方的消息。
window.addEventListener('message', function(e) {
console.log(e.data);
},false);
message
事件的事件对象 event
,提供以下三个属性。
event.source
:发送消息的窗口event.origin
: 消息发向的网址event.data
: 消息内容下面的例子是,子窗口通过 event.source
属性引用父窗口(注意这个引用只有传递消息的功能,没有 window 的其他权限),然后发送消息。
window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
event.source.postMessage('Nice to see you!', '*');
}
event.origin
属性可以过滤不是发给本窗口的消息。
window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
if (event.origin !== 'http://aaa.com') return;
if (event.data === 'Hello World') {
event.source.postMessage('Hello', event.origin);
} else {
console.log(event.data);
}
}
借助 CDM(message 与 postMessage),我们便可以通过消息的传输与消息事件来间接地实现跨域的 LocalStorage
共享、联动,由此推而广之,Cookie 等的跨域也水到渠成了。
所以 CDM 是终极之道!
Ajax 为什么会受到跨域限制呢?
当我们在 A 域下使用 Ajax 向 B 域的某个接口发起请求时,浏览器会为这个 Ajax 请求带上 B 域下的 Cookie,假设刚好该 Cookie 里有登录态,那么此时 B 域所在服务器在没做辨别的情况下就会如实返回数据。假设返回的数据存在一些安全性、私密性的数据,那么 A 域也就拿到了这些数据,此时这些数据就危险了。
所以浏览器为了保护可能的安全、隐私泄露,浏览器在接收到 Ajax 请求的数据之后会进行同源与 CORS(见下文)鉴定,只有符合要求才会将数据正常转交给页面使用。
所以,服务器一般是一视同仁(无法辨别是用户行为请求,还是 Ajax 请求),只要符合要求就返回数据给你,是浏览器为我们的安全考虑了同源策略。即,这些跨域的 AJAX 请求确实是发出去了的,服务器也确实接收到了,但是浏览器收到数据后不给你。
在浏览器部署,对于请求的 response
头添加 Access-Control-Allow-Origin
字段指定信任的域。这个字段的主要目的是:告知浏览器我这个 response
对那些域是信任的,在这些域下发起的 AJAX 请求不用拦截数据。
此方案需要在浏览器部署,一般用在专门的 API 服务商。
其他方案
参考:http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html