本文所有案例在本地址都可找到:https://github.com/dancingZhou/sameOrigin/tree/dev
什么是同源策略
两个页面地址中的协议、域名和端口号一致,则表示同源。
例如该地址 https://www.google.com 和以下地址对比
地址 | 同源 | 原因 |
---|---|---|
http://www.google.com | 否 | 协议不一致 |
https://google.com | 否 | 域名不一致 |
https://www.google.com:81 | 否 | 端口号不一致 |
https://www.google.com/a/s.html | 是 | 协议,域名和端口号都一致 |
同源策略的限制:
-
存储在浏览器中的数据,如localStroage、Cooke和IndexedDB不能通过脚本跨域访问
-
不能通过脚本操作不同域下的DOM
-
不能通过ajax请求不同域的数据
为什么要同源策略
设置同源限制主要是为了安全,如果没有同源限制存在浏览器中的Cookie等其他数据可以任意读取,不同域下DOM任意操作,Ajax任意请求的话如果浏览了恶意网站那么就会泄漏这些隐私数据。
Cookie一般用来保存登录状态。在登录一个银行网站后此时浏览器中就保存了登录的状态,同时浏览了恶意网站,这时Cookie的信息没有同源限制的话恶意网站就可以获取这些Cookie信息来达到不为人知的目的。
如果可以操作不同域下的DOM可以用如下方式完成盗取信息。在自己的网站上嵌入一个iframe地址设置成银行地址,然后让iframe全屏显示,当你一不小心上当了输入你账号密码,我就可以通过DOM操作获取到输入的信息。
Ajax的限制同Cookie,如果带上Cookie去跨域访问接口就可以通过程序的验证被认为身份是合法的。
既然瞥见危害一角自然要严加防范,限制非同源操作。
怎么规避同源策略
在看法一个网站的过程中有的数据并不在同一台服务器上这时怎么跨域调用就是一个很棘手的问题,可以通过以下几个方式来规避同源的限制。
DOM同源策略的规避
hash
因为hash的改变并不会引起页面的刷新同时可以通过 window.onhashchange
事件监听到hash的改变,所以可以通过hash来跨域传递数据。
<!-- http://example.com/index.html -->
<html><head><meta charset="utf8"/><title>跨域DEMO --- hash</title></head><body><iframe id="iframe" src="http://example2.com/index.html"></iframe><script>var ifra = document.getElementById('iframe');ifra.onload = function(){// ifra 加载完成了ifra.src = ifra.src + '#data';}</script></body>
</html>
<!-- http://example2.com/index.html -->
<!-- 在iframe中的页面(example2.com)如果和iframe所在页面(example.com)不同域是不能获取所在页面的DOM,然后通过hash将数据传递回去的,也就是说如果同域就可以通过该方法向所在页面传递数据 -->
<html><head><meta charset="utf8"/><title>跨域DEMO --- hash</title></head><body><script>window.onhashchange = function(){// 打印通过hash传过来的数据console.log( location.hash ); }</script></body>
</html>
该方法会直接暴露所传递的数据并且对所传数据有大小限制。
document.domain
若两个文档的域相同则可以获取对方的DOM对象,并且可以通过设置 document.domain 的值来让两个文档的域保持一致,但是 document.domain 并不是可以设置任何值,只能设置为当前域的超域,比如:
m.example.com 设置为 example.com,并且不能 example.com 设置为 m.example.com 也不能将 m.example.com 设置为 example2.com。
所以document.domain只可以在拥有相同的主域名的不同子域名之间跨域。
<!--http://a.example.com-->
<html><head><meta charset="utf8"/><title>跨域DEMO --- document.domain</title></head><body><p id="data">我在 a.example.com 下</p><iframe id="ifra" src="http://b.example.com/index.html"></iframe><script>// 这里为什么也要设置呢?因为document.domain的赋值会导致端口被覆盖成null,并且js中没有手段单独设置端口,所以这里设置一遍这样就和iframe中的一致了。document.domain = 'example.com';var ifra = document.getElementById('ifra');ifra.onload = function(){console.log( ifra.contentWindow.document.getElementById('data').innerHTML ); // 我是b.example.com下的}</script></body>
</html>
<!--http://b.example.com-->
<html><head><meta charset="utf8"/><title>跨域DEMO --- document.domain</title></head><body><p id="data">我是b.example.com下的</p><script>document.domain = 'example.com';console.log( parent.document.getElementById('data').innerHTML ); // 我在 a.example.com 下</script></body>
</html>
window.name
window.name有一个特性,即使当前窗口的地址改变了window.name的值也不会改变。可以利用这一特性来进行跨域,步骤如下:
- 通过iframe加载需要获取数据的地址
- 在加载的文件上将数据设置到window.name上
- 数据获取完成后将iframe的地址设置为当前文档同域
- 通过DOM操作拿到window.name上的数据
<!--http://example.com-->
<html><head><meta charset="utf8"/><title>跨域DEMO --- window.name</title></head><body><iframe id="ifra" src="http://example2.com/index.html"></iframe><script>var retData = false;var ifra = document.getElementById('ifra');ifra.onload = function(){if( !retData ){ifra.src = 'http://example.com/index.html'retData = true;}else{console.log( ifra.contentWindow.name ); // 我在example2.com下}}</script></body>
</html>
<!--http://example2.com-->
<html><head><meta charset="utf8"/><title>跨域DEMO --- window.name</title></head><body><script>window.name = '我在example2.com下';</script></body>
</html>
window.postMessage
以上几种跨域的方法都属于破解行为,而postMessage是H5为跨域提供的解决方法。
otherWindow.postMessage(message, targetOrigin[, transfer])
<!--http://example.com-->
<html><head><meta charset="utf8"/><title>跨域DEMO --- window.postMessage</title></head><body><iframe id="ifra" src="http://example3.com/index.html"/><script>var ifra = document.getElementById('ifra');ifra.onload = function(){ifra.contentWindow.postMessage('我来自example.com', 'http://example3.com')}</script></body>
</html>
<!--http://example3.com-->
<html><head><meta charset="utf8"/><title>跨域DEMO --- window.postMessage</title></head><body><iframe src="http://example2.com/index.html"/><script>window.addEventListener('message', function(messageEvent){console.log( messageEvent.data ); // 我来自example.com}, false)</script></body>
</html>
messageEvent对象上的属性中有三个属性要注意,分别是:
- source 发送消息的窗体
- origin 发送消息的域名 (根据域名判断是否处理该消息)
- data 发送消息的内容 (获取发送的消息内容)
Ajax同源策略的规避
jsonp
虽然跨域限制了Ajax请求,但是却并不影响跨域引用脚本。
<script>function callback (data) {console.log(data); // 上面的加载完成之后就会打印出后台传过来的数据 "数据"}
</script>
<script src="http://example.com/index.php?arg=val1&jsonp=callback"></script>
<?phpecho $_GET['jsonp'] . '(' . '数据' . ')';
?>
上面的 index.php 接口返回的是一段调用 函数的js代码 callback(data),其中data就是接口要返回的数据。而这个callback是事先在页面上写好的处理数据的回调函数,并且回调函数的名称通过URL参数的形式传递给了后端接口,这样就完成了一次跨域。
注:jsonp只支持GET请求。
CORS
cors(跨域资源共享)
参考
- https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy
- https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage
- http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html