平常开发中最常与网络打交道,那关于浏览器的同源策略和跨域相关的知识是该整理一下了。
首先须要明确的是,同源策略是浏览器的安全策略,因为存在这个策略,咱们才须要对各类跨域需求进行处理。html
同源策略的主要目的是为了保护用户的信息安全。前端
同源的含义其实比较好理解,实际上就是三点ajax
url | 说明 | 是否同源 |
---|---|---|
http://www.test.com https://www.test.com | 同一域名,不一样协议 | 不一样源 |
http://www.test.com https://www.test1.com | 不一样域名 | 不一样源 |
http://www.test.com:8080 https://www.test.com:8081 | 同一域名,不一样端口 | 不一样源 |
https://www.test.com http://192.168.1.1 | 域名和域名对应的 ip | 不一样源 |
一、Cookie、localStorage 和 IndexDB 没法读取
二、DOM 没法得到
三、AJAX 请求不能发送算法
cookie 的读取与其余属性有些不一样。由于 cookie 的可见性是由 domain 属性和 path 属性决定的,因此他其实不受协议和端口的限制。只要是同一个 domain 和可见 path 下的 cookie,都能读取到。segmentfault
因为 cookie 的读取自己具备局限性,因此跨域后的规避能够这样操做跨域
Uncaught DOMException: Failed to set the 'domain' property on 'Document': 'test' is not a suffix of 'localhost'.
localStorage、IndexedDB 和 DOM 是实实在在受同源策略限制的,协议、域名和端口任意一项不相同就没法读取。
其实对于他们的读取能够简化成不一样源的两个页面如何通讯。
自己 localStorage 和 IndexedDB 的出现就是为了让咱们可以更好地存储数据,而获取 DOM 大多状况也是为了获取 DOM 中的各类数据。泛化到业务场景中,也就是传递数据的事儿了。浏览器
目前能够经过如下几种方式来进行通讯。安全
window.name
使用 window.name 的原理是同源 iframe 能够读取 contentWindow。具体操做以下
一、a 页面先载入一个不一样源的 iframe 页面 b
二、在 b 页面 中修改 window.name 为须要的传递的数据
三、a 页面中修改这个 iframe 的 src 为同源的页面 c
四、a 页面获取以前设置的 window.name
按照这样的方式进行操做就能获取到子页面中的数据。若是子页面想要获取父页面中的数据,能够将 一、3 步骤换一下,2 步骤改为父页面直接修改 iframe.contentWindow.name 为须要传递的数据便可。
由于是经过 name 属性来传递参数,因此可传递的数据量很大,基本就是字符串长度的最大值。服务器
//记录 iframe onload 事件的加载次数 var state = 0; var iframe = document.createElement("iframe"); // 加载跨域页面 iframe.src = "http://sub.test.com/test/test.html"; // onload 事件会触发2次,第1次加载跨域页,在跨域页中将所须要的数据赋给 window.name iframe.onload = function () { if (state === 1) { // 第2次 onload 成功后,读取同域 window.name 中数据 var data = iframe.contentWindow.name; } else if (state === 0) { // 第1次onload(跨域页)成功后,切换到同域代理页面 iframe.contentWindow.location = "./testB.html"; state = 1; } }; document.body.appendChild(iframe);
fragment identifier 片断标识符
fragment identifier 其实就是 url # 后面的部分。常写 SPA 应用的小伙伴应该会对他很熟悉,由于 hash 模式的 router 就是基于他实现的。修改嵌入的 iframe 的 src 为 url#须要传递的数据,iframe 页面就能经过监听 hashchange 事件得到传递的数据。若是是子页面向父页面传递数据要多加一步,得先让父页面把本身当前的 url 传递给子页面,而后子页面去修改父页面的 href。
使用这个方法传递数据的限制暂时还未测试,猜想应该会和浏览器限制 url 的长度有关。cookie
//父->子 a页面 var iframe = document.createElement("iframe"); // 加载跨域页面 iframe.src = url; setTimeout(function () { iframe.src = url + "#a=111"; }, 1000); document.body.appendChild(iframe); //父->子 b页面 window.onhashchange = function () { alert(window.location.hash) }
//子->父 a页面 var iframe = document.createElement("iframe"); // 加载跨域页面 iframe.src = url; setTimeout(function () { var selfurl = document.location.href; iframe.src = url + "#" + selfurl; }, 1000); document.body.appendChild(iframe); window.onhashchange = function () { alert("from son" + window.location.hash); }; //子->父 b页面 window.onhashchange = function () { let target = window.location.hash.slice(1); window.parent.parent.location.href = target + "#111aaaa" }
postMessage
严格意义上来讲上面两种方法都是对跨域页面获取数据的破解,postMessage 才是正统的非同源页面之间传递数据的方法。
window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。
postMessage 是新增的 API,使用前记得查看一下兼容性。
使用上须要注意的就是调用 postMessage 和监听 message 事件的的主体是同一个。也就是说子页面向父页面发送消息时,须要获取 window.parent 去调用 postMessage。父页面向子页面发送消息时,能够直接获取 iframe,而后调用 postMessage。
postMessage((message, targetOrigin, [transfer]);
message
将要发送到其余 window 的数据。它将会被结构化克隆算法序列化。这意味着你能够不受什么限制的将数据对象安全的传送给目标窗口而无需本身序列化。
targetOrigin
经过窗口的 origin 属性来指定哪些窗口能接收到消息事件,其值能够是字符串""(表示无限制)或者一个 URI。
在发送消息的时候,若是目标窗口的协议、主机地址或端口这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会被发送;只有三者彻底匹配,消息才会被发送。
这个机制用来控制消息能够发送到哪些窗口;例如,当用 postMessage 传送密码时,这个参数就显得尤其重要,必须保证它的值与这条包含密码的信息的预期接受者的 origin 属性彻底一致,来防止密码被恶意的第三方截获。
若是你明确的知道消息应该发送到哪一个窗口,那么请始终提供一个有确切值的 targetOrigin,而不是。不提供确切的目标将致使数据泄露到任何对数据感兴趣的恶意站点。
transfer 可选
是一串和message 同时传递的 Transferable 对象. 这些对象的全部权将被转移给消息的接收方,而发送一方将再也不保有全部权
下面这个示例是子页面向父页面发送消息。
// a 页面 window.addEventListener("message", function (event) { console.log(event); }, false); // b 页面 var b = "bbb"; window.parent.postMessage({ b }, "*")
另外特别须要注意的是关于 DOM 的获取,若是只是两个不一样子域的页面,将 document.domain 设置为同一主域就能够读取相应数据。
//a 页面 main.test.com var a = "aaa"; // domain 设置为主域 document.domain = "test.com"; var iframe = document.createElement("iframe"); // 加载跨域页面 iframe.src = "sub.test.com"; document.body.appendChild(iframe); iframe.onload = function(){ // 能够获取到变量 var data = iframe.contentWindow.b // 能够获取到 DOM var dom = iframe.contentWindow.document.body; } //b 页面 sub.test.com var b = "bbb"; // domain 设置为主域 document.domain = "test.com";
AJAX 请求跨域平常使用比较多,经常使用的方法有如下几种
JSONP
这个方法是向服务器请求的时候,在 url 后面写上 callback 方法的名字,请求返回其实是返回了一个 调用 callback 方法的 js 文件。须要返回的参数也就在调用的时候传进去了。
因此局限也很明确,只支持 get 方法。
使用代理服务器
全部的请求先发送给这个代理服务器,由这个代理服务器去请求实际的接口,再把须要的数据返回。
(这个方式就能够真切地体会到,同源策略只是浏览器的一种安全策略)
使用 CORS Cross-Origin Resource Sharing 跨域资源共享
目前基本上全部的浏览器都支持 CORS,因此只须要服务器端进行处理。对于前端来讲,请求和同源的 AJAX 请求是一致的。
前端发送请求的时候浏览器会自动带上 origin 字段,服务器端去判断这个 origin 是不是可接受的地址,在相应头中设置 Access-Control-Allow-Origin 字段的值。
这样前端就能正常获取到数据啦。
这里只对 CORS 作了一个简单介绍,详细的下次细说吧。
总结:
浏览器同源政策及其规避方法
浏览器的同源策略
window.postMessage
跨源资源共享(CORS)
前端常见跨域解决方案(全)