上面是Wikipedia对同源策略的定义,简单来说同源策略就是限制不同的源(Domain)之间的交互的安全策略。想象如果没有同源策略的限制,你访问银行的网站A时的登录信息,在你访问另一个网站B的时候,B网站可以拿到A网站的信息,如果B网站有恶意的代码,就可以直接访问你的银行账户。
同源策略中的同源,是三个相同:
以http://www.example.com为例:
| URL | Outcome | Reason |
|---|---|---|
| http://www.example1.com | Failure | 域名不同 |
| http://www.example.com:8080 | Failure | 端口号不同 |
| http://www.example.com/dir/page.html | Same Origin | 仅仅是path不同 |
| https://www.example1.com | Failure | 协议不同 |
同源策略对跨源访问的控制可以分为三类;
写操作是可以,例如表单提交、重定向这些不受同源策略的限制对于开发人员来说,第三条限制,跨域资源之间的读取受到限制,这个是最常遇见的问题,对于第二条中一些HTML原生标签不受跨域访问的限制,可以用来hack掉同源策略的限制,以下资源是不受同源策略限制的:
<script>标签外部CSS的加载<link rel="stylesheet" href="..."><Img>标签<video>和<audio>标签<object>、<embed>(<applet>也不受同源策略限制,但该标签已经被废弃)@font-face引用的字体(浏览器实现有差异,有些要同源)<iframe>中嵌入的内容在网页上可以通过设置document.domain设置当前页面所在的域,例如,在页面http://store.company.com/dir/page.html中的JS脚本执行了下面的代码:
document.domain = "company.com";那么在当前网站上去获取http://www.company.com的网站上的资源是不受同源策略的限制的,但是这个改变domain的方法仅仅用于二级域名获取一级域名的资源的时候,你不能在http://www.company.com的domain改变为otherCompany.com。
iframe在的父子窗口如果不是同源的话,在访问获取彼此的DOM节点或者读取数据的话,会受到同源策略的限制,无法访问。
父窗口所在的域为http://localhost:5500,其中子窗口所在的域为http://localhost:8080,这里父子窗口是同源的,所以彼此在获取资源的时候,会受到同源策略的限制:
对于iframe额跨域问题,有三种解决办法:
fragement(hash tag)postMessage
这里的fragement指的是url中#后面的部分,在父窗口中把信息作为hash写入到子窗口的src上。
const originalSrc = document.getElementById('myFrame').src;
const src = originalSrc + '#' + data;
document.getElementById('myFrame').location.replace(src);因为url hash的改变不会引起页面的刷新,但是会触发onhashchange的事件,在子窗口中监听onhashchange的事件。
function getData() {
const data = window.location.hash;
......
}
window.onhashchange = getData同样,子窗口也可以通过同样的方式,向父窗口传递数据:
parent.location.replace(parentUrl + "#" + data);window.namewindow.name在页面进行跳转前设置的值,在跳转后也可以访问到,我们可以借助window.name的这个特性来解决跨域问题,整个流程如下:
iframe的src,在子窗口load成功后将data挂到window.name上,然后设置子窗口的location.href到与父窗口同域的页面跳转到同域的页面后,父窗口就可以通过window.name拿到跨域页面设置的数据了
例如:
a.com/index.html // {A}
b.com/index.html // {B}
a.com/empty.html // {C}我们在A中加载完成后,在A页面中设置src为B页面的url,在iframe加载成功后,将数据设置到window.name上,然后改变自己的location.href为C页面,C页面加载成功后,A页面和B页面就是同源的了,这个时候A页面去子窗口上取window.name的值就不会因为跨域问题而报错了。 使用window.name的关键是在设置完window.name后,要将页面跳转到与父窗口同源的页面,这样主窗口才能访问子窗口上设置的数据
html5提供了postMessage的机制,用于不同源之间的通信:
在父窗口中监听message事件
iframe窗口和父窗口不是同源的,通过postMessage将消息发送给父窗口。
在浏览器中,script标签是可以从不同源的地址上加载而不受同源策略的限制,这就给我们提供了一种手段,绕过同源策略的限制,JSONP就是通过这种手段来达到跨域通信的,但是JSONP需要服务端的支持,将返回的JSON数据和url中的callback拼接起来,只要前端定义了这个函数,在浏览器看来,就是从服务端加载了一段带数据的函数调用。
CORS(cross-origin resource share)规范了跨域请求的标准,通过HTTP头让浏览器允许跨域请求的发送,这个话题,我会在下一篇博客细讲。
兴趣遍地都是,坚持和持之以恒才是稀缺的