跨域解決方案
跨域解決方案有:設(shè)置document.domain,使用帶src標(biāo)簽,JSONP,navigation對象,CORS,window.postMessage,片段標(biāo)識符,window.name,WebSocket
設(shè)置document.domain
- 原理:相同主域名不同子域名下的頁面,可以設(shè)置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);
需要設(shè)置iframe的domain,將 document.domain往上設(shè)置一級,這樣即可操作DOM和Cookie
document.domain = 'a.com'
使用帶src標(biāo)簽
- 原理:所有具有src屬性的HTML標(biāo)簽都是可以跨域的,包括
<img>,<script>,<iframe> - 限制:只能用于GET方法
JSONP
利用script標(biāo)簽可以跨域這點(diǎn),跨域獲得的腳本包含一個(gè)客戶端和服務(wù)器端約定好的回調(diào)函數(shù),以及服務(wù)器端發(fā)送的數(shù)據(jù)。
jQuery實(shí)現(xiàn)
//URL具有callback參數(shù)時(shí), jQuery將會把它解釋為一個(gè)JSONP請求,創(chuàng)建一個(gè)<script>標(biāo)簽來完成該請求。
$.getJSON( "http://b.a.com/bar?callback=callback", function( data ){
// 處理跨域請求得到的數(shù)據(jù)
});
JS實(shí)現(xiàn)
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)
- 原理:服務(wù)器設(shè)置Access-Control-Allow-OriginHTTP響應(yīng)頭之后,瀏覽器將會允許跨域請求,H5推薦的跨域方式。
- 限制:瀏覽器需要支持HTML5,可以支持POST,PUT等方法
跨域發(fā)送Cookie
將xhr的屬性withCredentials設(shè)置為true后,即可攜帶目標(biāo)域的Cookie
// 原生
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.withCredentials = true;
xhr.send();
// jQ
$.ajax({
url: a_cross_domain_url,
xhrFields: {
withCredentials: true
}
});
還需要服務(wù)器端設(shè)置Access-Control-Allow-Credentials響應(yīng)頭為true,并且將Access-Control-Allow-Origin設(shè)置為請求對應(yīng)的域名
既然Access-Control-Allow-Origin只允許單一域名, 服務(wù)器可能需要維護(hù)一個(gè)接受 Cookie 的 Origin 列表, 驗(yàn)證 Origin 請求頭字段后直接將其設(shè)置為Access-Control-Allow-Origin的值。 (這一實(shí)踐來自 Stackoverflow) 值得注意的是在 CORS 請求被重定向后 Origin 頭字段會被置為 null。 此時(shí)可以選擇從Referer頭字段計(jì)算得到Origin。
preflight
對于非簡單請求,CORS 機(jī)制跨域會首先進(jìn)行 preflight(一個(gè) OPTIONS 請求), 該請求成功后才會發(fā)送真正的請求。 這一設(shè)計(jì)旨在確保服務(wù)器對 CORS 標(biāo)準(zhǔn)知情,以保護(hù)不支持 CORS 的舊服務(wù)器。

window.postMessage
- 原理:HTML5允許窗口之間發(fā)送消息
- 限制:瀏覽器需要支持HTML5,獲取窗口句柄后才能相互通信
這是一個(gè)安全的跨域通信方法,postMessage(message,targetOrigin)也是HTML5引入的特性。 可以給任何一個(gè)window發(fā)送消息,不論是否同源。第二個(gè)參數(shù)可以是*但如果你設(shè)置了一個(gè)URL但不相符,那么該事件不會被分發(fā)。
// 頁面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);
});
片段標(biāo)識符
- 原理:改變網(wǎng)頁#后面的部分,被改變的網(wǎng)頁可以通過監(jiān)聽onhashchange事件得到通知
- 限制:URL具有長度的限制,只能傳送字符串
// A窗口
var src = originURL + '#' + data;
document.getElementById('myIFrame').src = src;
// B窗口
window.onhashchange = function(){
var message = window.location.hash;
}
window.name
- 原理:只要在同一個(gè)窗口里,前一個(gè)網(wǎng)頁設(shè)置了這個(gè)屬性,后一個(gè)網(wǎng)頁可以讀取它。
- 限制:必須監(jiān)聽子窗口window.name屬性的變化,影響網(wǎng)頁性能。
WebSocket
- 原理:WebSocket使用ws、wss作為通信協(xié)議,改協(xié)議沒有同源策略
- 限制:需要瀏覽器支持
參考文獻(xiàn)
JS高程(第三版)
Web開發(fā)中跨域的幾種解決方案
瀏覽器同源策略及其規(guī)避方法
CORS 跨域發(fā)送 Cookie
CORS 跨域中的 preflight 請求