什么是跨域
两个页面的协议、域名、端口,三者之一不同的话,两个页面就是不同源的。不同源的页面会有跨域问题。
如果非同源,以下三种会受到限制
(1) Cookie、LocalStorage 和 IndexDB 无法读取。
(2) DOM 无法获得。
(3) AJAX 请求不能发送。
跨域的类型和解决方式
主域相同的跨域
如果两个页面的主域名相同,比如 static.a.com 和 info.a.com 可以将这两个页面的 document.domain 都设置成 a.com 即可以通过同源检测。
(1) 在www.a.com/a.html中:
document.domain = 'a.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://www.script.a.com/b.html';
ifr.display = none;
document.body.appendChild(ifr);
ifr.onload = function(){
var doc = ifr.contentDocument
ifr.contentWindow.document;
ifr.onload = null;
};
(2) 在www.script.a.com/b.html中:
document.domain = 'a.com';
//注意:使用document.domain允许子域安全访问其父域时,您需要设置document域在父域和子域中具有相同的值。这是必要的,即使这样做只是将父域设置回其原始值。否则可能会导致权限错误。这里都是a.com。
完全不同源的跨域(两个页面之间的通信)
通过 location.hash 跨域
a.com 下的 c1.html 和 jianshu.com 下的 c2.html 1、c1.html 自动创建一个隐藏的 iframe ,src 指向 c2.html
2、c2.html 响应请求后修改 c1.html 的 hash 值
3、c1.html 加一个定时器,轮询监听 location.hash 有无变化
优点:1.可以解决域名完全不同的跨域。2.可以实现双向通讯。
缺点:location.hash会直接暴露在URL里,并且在一些浏览器里会产生历史记录,数据安全性不高也影响用户体验。另外由于URL大小的限制,支持传递的数据量也不大。有些浏览器不支持onhashchange事件,需要轮询来获知URL的变化。
通过 window.name 跨域
window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的。window.name属性的神奇之处在于name 值在不同的页面(甚至不同域名)加载后依旧存在(如果没修改则值不会变化),并且可以支持非常长的 name 值(2MB)。
window.name = data;//父窗口先打开一个子窗口,载入一个不同源的网页,该网页将信息写入。
location = 'http://parent.url.com/xxx.html';//接着,子窗口跳回一个与主窗口同域的网址。
var data = document.getElementById('myFrame').contentWindow.name。//然后,主窗口就可以读取子窗口的window.name了。
如果是与iframe通信的场景就需要把iframe的src设置成当前域的一个页面地址。
所以
1、 a.com 下的 c1.html 打开 iframe 地址为 jianshu.com 下的 c2.html
2、在 c1.html 设置 window.name ,由他打开的所有页面都可以获取和修改 window.name
通过 window.postMessage 跨域
iframe 内页面向外部发送消息,简单写法:
// iframe 内
window.parent.postMessage({aa: 123}, 'http://static.synapse.xin');
// 父级页面
window.addEventListener('message', function(e){
console.log(e);
})
aaa.com 打开 bbb.com 并向 bbb 发送消息
父级页面打开子页面并发送消息,子页面收到并回复消息,考虑到兼容性的写法:
// 父级页面
var popup = window.open('http://bbb.com', 'title');
popup.postMessage({aa: 123}, 'http://bbb.com');
// 子级页面
function onmessage(e) {
console.log(e);
// e.data 发送的数据 {aa: 123}
// e.origin 消息来源的地址 http://aaa.com
// e.source 消息来源的 window 对象
if(e.origin == 'http://aaa.com'){
e.source.postMessage('nice to see you', '*')
}
}
if(typeof window.addEventListener != 'undefined' ){
window.addEventListener('message', onmessage);
}else if(typeof window.attachEvent != 'undefined'){
window.attachEvent('onmessage', onmessage);
}
AJAX请求不同源的跨域
通过JSONP跨域
基本原理:网页通过添加一个
function todo(data){
console.log('The author is: '+ data.name);
}
var script = document.createElement('script');
script.src = 'http://www.jianshu.com/author?callback=todo';
//向服务器www.jianshu.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字。
document.body.appendChild(script);
//服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。
todo({"name": "fewjq"});
//由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了todo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象。
优点:兼容老浏览器,后台改动小
缺点:只支持 GET 请求
通过WebSocket跨域
WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨域通信。
WebSocket请求头中有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。
正是因为有了Origin这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内。
通过CORS跨域
CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET请求,CORS允许任何类型的请求。
两种请求
请求分为简单请求和非简单请求
(1) 请求方法是以下三种方法之一:
- HEAD
- GET
- POST
(2)HTTP的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain
同时满足以上两个条件的,为简单请求
简单请求
浏览器如果发现这次跨域的 ajax 请求是简单请求,会自动在请求头中添加 Origin 字段,内容是协议+域名+端口
浏览器会检查响应 Access-Control-Allow-Origin 字段的值是否和 Origin 字段值相同,或者为 *,如果是的话允许跨域
非简单请求
比如说 Content-Type 是 application/json。
在发送真正的请求之前,浏览器会先发送一个 OPTION 请求(预检请求),同样检查 Access-Control-Allow-Origin 字段。通过后正常发请求。