跨域解决方案
跨域解决方案有:设置document.domain,使用带src标签,JSONP,navigation对象,CORS,window.postMessage,片段标识符,window.name,WebSocket
设置document.domain
- 原理:相同主域名不同子域名下的页面,可以设置document.domain让它们同域
- 限制:同域document提供的是页面间的互操作,需要载入iframe页面
// URL http://a.com/foo
var ifr = document.createElement('iframe');
ifr.src = 'http://b.a.com/bar';
ifr.onload = function(){
var ifrdoc = ifr.contentDocument || ifr.contentWindow.document;
ifrdoc.getElementsById("foo").innerHTML);
};
ifr.style.display = 'none';
document.body.appendChild(ifr);
需要设置iframe的domain,将 document.domain往上设置一级,这样即可操作DOM和Cookie
document.domain = 'a.com'
使用带src标签
- 原理:所有具有src属性的HTML标签都是可以跨域的,包括
<img>
,<script>
,<iframe>
- 限制:只能用于GET方法
JSONP
利用script标签可以跨域这点,跨域获得的脚本包含一个客户端和服务器端约定好的回调函数,以及服务器端发送的数据。
jQuery实现
//URL具有callback参数时, jQuery将会把它解释为一个JSONP请求,创建一个<script>标签来完成该请求。
$.getJSON( "http://b.a.com/bar?callback=callback", function( data ){
// 处理跨域请求得到的数据
});
JS实现
function loadJsonp(url,callback){
var script = document.createElement('script'),
rand = Math.random().toString().substring(2, 8),
functionName = "getJsonStr" + rand;
script.src = url + "?callback=" + functionName;
window[functionName] = function(data) {
if (callback) {
callback(data);
}
try {
delete window[functionName];
} catch (e) {
window[functionName] = undefined;
}
};
var head = document.getElementsByTagName('head')[0];
script.onload = function() {
script.onload = undefined;
head.removeChild(script);
};
script.onerror = function(e) {
console.error(e);
};
head.appendChild(script);
}
跨域资源共享(CORS)
- 原理:服务器设置Access-Control-Allow-OriginHTTP响应头之后,浏览器将会允许跨域请求,H5推荐的跨域方式。
- 限制:浏览器需要支持HTML5,可以支持POST,PUT等方法
跨域发送Cookie
将xhr的属性withCredentials设置为true后,即可携带目标域的Cookie
// 原生
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.withCredentials = true;
xhr.send();
// jQ
$.ajax({
url: a_cross_domain_url,
xhrFields: {
withCredentials: true
}
});
还需要服务器端设置Access-Control-Allow-Credentials
响应头为true,并且将Access-Control-Allow-Origin
设置为请求对应的域名
既然Access-Control-Allow-Origin只允许单一域名, 服务器可能需要维护一个接受 Cookie 的 Origin 列表, 验证 Origin 请求头字段后直接将其设置为Access-Control-Allow-Origin的值。 (这一实践来自 Stackoverflow) 值得注意的是在 CORS 请求被重定向后 Origin 头字段会被置为 null。 此时可以选择从Referer头字段计算得到Origin。
preflight
对于非简单请求,CORS 机制跨域会首先进行 preflight(一个 OPTIONS 请求), 该请求成功后才会发送真正的请求。 这一设计旨在确保服务器对 CORS 标准知情,以保护不支持 CORS 的旧服务器。
window.postMessage
- 原理:HTML5允许窗口之间发送消息
- 限制:浏览器需要支持HTML5,获取窗口句柄后才能相互通信
这是一个安全的跨域通信方法,postMessage(message,targetOrigin)也是HTML5引入的特性。 可以给任何一个window发送消息,不论是否同源。第二个参数可以是*
但如果你设置了一个URL但不相符,那么该事件不会被分发。
// 页面A,URL: http://a.com/foo
var win = window.open('http://b.com/bar');
win.postMessage('Hello, bar!', 'http://b.com');
// 页面B,URL: http://b.com/bar
window.addEventListener('message',function(event) {
console.log(event.data);
});
片段标识符
- 原理:改变网页#后面的部分,被改变的网页可以通过监听onhashchange事件得到通知
- 限制:URL具有长度的限制,只能传送字符串
// A窗口
var src = originURL + '#' + data;
document.getElementById('myIFrame').src = src;
// B窗口
window.onhashchange = function(){
var message = window.location.hash;
}
window.name
- 原理:只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。
- 限制:必须监听子窗口window.name属性的变化,影响网页性能。
WebSocket
- 原理:WebSocket使用ws、wss作为通信协议,改协议没有同源策略
- 限制:需要浏览器支持
参考文献
JS高程(第三版)
Web开发中跨域的几种解决方案
浏览器同源策略及其规避方法
CORS 跨域发送 Cookie
CORS 跨域中的 preflight 请求