關於跨域簡單總結

什麼是瀏覽器同源策略?

同源是指,域名,協議,端口號均相同,如圖:javascript

圖片描述

注意:localhost和127.0.0.1雖然都指向本機,但也是跨域.

瀏覽器同源策略(same-origin policy).簡單的講,同源策略就是瀏覽器爲了保證用戶安全,防止惡意的網站盜取數據,禁止不一樣域之間的JS進行交互.會限制以下行爲:css

  • Cookie,LocalStorage 和indexDB沒法讀取.
  • DOM沒法獲取.
  • AJAX請求不能發送.

解決跨域的方法有以下幾種

CORS資源共享

CORS是跨域資源共享(Crocss-Origin Resource Sharing)的縮寫.是W3C標準.跨域資源共享機制容許Web應用服務器進行跨域訪問控制,從而使跨域傳輸得以安全進行.對於這個方式阮一峯老師有一篇文章專門講這個的,跨域資源共享CORS詳解可供參考.還有就是MDN裏有關於HTTP訪問控制的文檔.HTTP訪問控制CORShtml

簡單的說就是:前端

圖片描述

請求方在頭信息中多一個Origin字段來講明本次請求來自哪一個源.(協議+域名+端口)
服務器響應頭信息中會多幾個字段java

  • Access-Control-Allow-Origin

該字段是必須的。它的值要麼是請求時Origin字段的值,要麼是一個*,表示接受任意域名的請求.git

  • Access-Control-Allow-Credentials

該字段可選。它的值是一個布爾值,表示是否容許發送Cookie。默認狀況下,Cookie不包括在CORS請求之中。設爲true,即表示服務器明確許可,Cookie能夠包含在請求中,一塊兒發給服務器。這個值也只能設爲true,若是服務器不要瀏覽器發送Cookie,刪除該字段便可.github

  • Access-Control-Expose-Headers

該字段可選。CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。若是想拿到其餘字段,就必須在Access-Control-Expose-Headers裏面指定。上面的例子指定,getResponseHeader('FooBar')能夠返回FooBar字段的值。web

開發者須要在AJAX中設置withCredentials屬性爲true.

JSONP跨域

JSONP是JSON with Padding的簡稱.這是一種利用瀏覽器漏洞解決跨域的辦法.HTML腳本元素能夠不受瀏覽器同源策略的限制。即咱們請求HTML、CSS、JS文件時瀏覽器不會作限制。因此運用這個特色,經過加載外部js文件的方式來向其餘資源發出http請求。正是由於是請求文件,因此JOSNP只能GET,不能POST.簡單來講,JSONP方案就是頁面動態建立一個script標籤.chrome

<script src="www.baidu.com/data.js?callback=Callback">

而後服務端根據咱們傳過去的參數,進行處理.json

舉個例子:

callBackFunc = function(data){
    console.log(data);
};
var script = document.createElenent('script');
script.type = "text/javascript";
script.src = "http://www.localhost:8888/data.json?callBack= callBackFunc";
document.body.appendChild(script);

JSONP的缺點:

  1. 只能進行GET請求,沒法發送POST請求.
  2. 缺少錯誤異常處理機制,若是沒有調用成功,也沒有任何提示.例如404沒法檢測具體錯誤緣由.
  3. 嚴重安全漏洞,藉助JSONP可進行跨站僞造攻擊(CSRF).

服務器代理

瀏覽器有跨域限制,可是服務器沒有,因此能夠由服務器請求所要域的資源再返回給客戶端。

服務器可解決一切跨域問題.

window.postMessage方案

window.postMessage() 方法能夠安全地實現跨源通訊。一般,對於兩個不一樣頁面的腳本,只有當執行它們的頁面位於具備相同的協議(一般爲https),端口號(443爲https的默認值),以及主機 (兩個頁面的模數 Document.domain設置爲相同的值) 時,這兩個腳本才能相互通訊。window.postMessage() 方法提供了一種受控機制來規避此限制,只要正確的使用,這種方法就很安全。

window.postMessage() 方法被調用時,會在全部頁面腳本執行完畢以後(e.g., 在該方法以後設置的事件、以前設置的timeout 事件,etc.)向目標窗口派發一個 MessageEvent 消息。 該MessageEvent消息有四個屬性須要注意: message 屬性表示該message 的類型; data 屬性爲 window.postMessage 的第一個參數;origin 屬性表示調用window.postMessage() 方法時調用頁面的當前狀態; source 屬性記錄調用 window.postMessage() 方法的窗口信息。
關於這個的文章可參考window.postMessage

舉個例子:

頁面和其餘打開的新窗口的數據傳遞

//同域 - a頁面
<a href="./b.html" target="_blank">跳轉</a>
<script>
    window.addEventListener('message', function(event) {
        if(event.origin == 'http://hwc.home.com:3000') {   //同域與不一樣域,都是靠origin判斷
            console.log(event.data)
        }
    })
</script>

//b頁面
<script>
    document.getElementById('app').onclick = function() {
        var target = 'http://hwc.home.com:3000'
        opener.postMessage('b.html send message', target)
    }
</script>

頁面與嵌套的iframe消息

//a頁面
<iframe src="http://hwc.home.com:4000/home.html"></iframe>
var iframe = document.getElementsByTagName('iframe')[0];
window.onMessage = function(event) {
    if(event.source == iframe.contentWindow) {}   //嵌套iframe除了origin外還能夠比較用source
}

//iframe頁面
window.parent.postMessage(data, target)
window.addEventListener('message',function(e){
    if(e.source!=window.parent) return;     //子頁面能夠經過此方法判斷是不是父頁面的消息
    window.parent.postMessage('data','*');
},false);

多窗口之間消息傳遞不是指我用瀏覽器分別打開了兩個頁面,就能互相通訊了。須要一個otherWindow,一個其餘窗口的引用 window.postMessage

語法 ——> otherWindow.postMessage(message, targetOrigin, [transfer]);
otherWindow ——> 其餘窗口的一個引用,好比iframe的contentWindow屬性、執行window.open返回的窗口對象、或者是命名過或數值索引的window.frames。
message ——> 將要發送到其餘 window的數據
targetOrigin——> 經過窗口的origin屬性來指定哪些窗口能接收到消息事件,其值能夠是字符串"*"(表示無限制)或者一個URI。

iframe解決跨域

iframe跨域也是利用瀏覽器中帶有src的標籤能夠跨域訪問.凡是擁有src屬性的標籤都有跨域的能力.例如:<script> ,<img> ,<iframe>.

1) document.domain + iframe

對於主域相同,子域不一樣的狀況,能夠經過設置document.domain的辦法解決.具體的作法是能夠在http://www.a.com/a.htmlhttp://script.a.com/b.html兩個文件中分別加上document.domain = ‘a.com’;而後經過a.html文件中建立一個iframe,去控制iframe的contentDocument,這樣兩個js文件之間就能夠「交互」了。固然這種辦法只能解決主域相同而二級域名不一樣的狀況,若是你異想天開的把script.a.com的domian設爲alibaba.com那顯然是會報錯地!

主域名:就是指一我的經過域名註冊商註冊的那個名字(domain name),它在註冊商的系統中有個惟一的域名ID。例如www.baidu.com,主域名就是baidu.com.而主域名左邊的就是他的子域名.

代碼以下:
父窗口(http://www.a.com/a.html)

//<iframe id="iframe" src="http://script.a.com/b.html"></iframe>
<script>
    var iframe = document.createElement('iframe');
    iframe.src = 'http://script.a.com/b.html';
    iframe.style.display = 'none';
    document.body.appendChild('iframe');
    //設置domain值.
    document.domain = 'a.com';
    var user = 'admin';
</script>

子窗口(http://script.a.com/b.html)

<script>
    document.domain = 'a.com';
      // 獲取父窗口中變量
    console.log('data from parent=> ' + window.parent.user);
</script>

這樣就能在子窗口獲取父窗口裏的數據了.

一、安全性,當一個站點(b.a.com)被攻擊後,另外一個站點(c.a.com)會引發安全漏洞。
二、若是一個頁面中引入多個iframe,要想可以操做全部iframe,必須都得設置相同domain。

2) location.hash + iframe

對於主域不一樣的情形,可用location.hash 和window.name解決.

原理是利用location.hash來進行傳值。在url: http://a.com#segmentfault中的‘#segmentfault’就是location.hash,改變hash並不會致使頁面刷新,因此能夠利用hash值來進行數據傳遞,固然數據容量是有限的。假設域名a.com下的文件test1.html要和b.com域名下的test2.html傳遞信息,test1.html首先建立自動建立一個隱藏的iframe,iframe的src指向b.com域名下的test2.html頁面,這時的hash值能夠作參數傳遞用。test2.html響應請求後再將經過修改test1.html的hash值來傳遞數據(因爲兩個頁面不在同一個域下IE、Chrome不容許修改parent.location.hash的值,因此要藉助於a.com域名下的一個代理iframe;Firefox能夠修改)。同時在test1.html上加一個定時器,隔一段時間來判斷location.hash的值有沒有變化,一點有變化則獲取獲取hash值。

a.com下的test1.html

function startRequest(){
    var iframe = document.createElement('iframe');
    iframe.style.display = 'none';
   iframe.src = 'http://www.b.com/test2.html#paramdo';
    document.body.appendChild(iframe);
}

function checkHash() {
    try {
        var data = location.hash ? location.hash.substring(1) : '';
        if (console.log) {
            console.log('Now the data is '+data);
        }
    } catch(e) {};
}
setInterval(checkHash, 2000);

b.com域名下的test2.html

//模擬一個簡單的參數處理操做
switch(location.hash){
    case '#paramdo':
        callBack();
        break;
    case '#paramset':
        //do something……
        break;
}

function callBack(){
    try {
        parent.location.hash = 'somedata';
    } catch (e) {
        // ie、chrome的安全機制沒法修改parent.location.hash,
        // 因此要利用一箇中間的b.com域下的代理iframe
        var iframeproxy = document.createElement('iframe');
        iframeproxy.style.display = 'none';
        iframeproxy.src = 'http://a.com/test3.html#somedata'; 
        // 注意該文件在"a.com"域下
        document.body.appendChild(iframeproxy);
    }
}

a.com域名下的test3.html

//由於parent.parent和自身屬於同一個域,因此能夠改變其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);
缺點,暴露數據在url中.

3) window.name + iframe

對於主域不一樣的情形,可用location.hash 和window.name解決.

iframe的src屬性由外域轉向本地域,跨域數據即由iframe的window.name從外域傳遞到本地域。這個就巧妙地繞過了瀏覽器的跨域訪問限制,但同時它又是安全操做。

有三個頁面:

  • a.com/test1.html :應用頁面
  • a.com/proxy.html :代理頁面,須要和應用頁面在同一域下.
  • b.com/test2.html : 存放應用頁面須要用到數據的頁面(數據頁面)
具體過程:

1) 應用頁面建立一個iframe,src指向數據頁面.數據頁面會把數據附加到這個iframe的window.name上.

b.com/test2.html 代碼

<script type="text/javascript">
    window.name = 'I was data!';    
    // 這裏是要傳輸的數據,大小通常爲2M,IE和firefox下能夠大至32M左右
    //數據格式能夠自定義,如json、字符串
</script>

2) 在應用頁面中監聽iframe的onload事件,在事件中設置這個iframe的src指向本地域的代理文件(代理頁面和應用頁面在同一域下,能夠相互通訊.)
a.com/test1.html 代碼

<script type="text/javascript">
    var state = 0, 
    iframe = document.createElement('iframe'),
    loadFunction = function() {
        if (state === 1) {
            var data = iframe.contentWindow.name;    // 讀取數據
            console.log(data);    //打印'I was data!'
        } else if (state === 0) {
            state = 1;
            iframe.contentWindow.location = "http://a.com/proxy.html";    // 設置的代理文件
        }  
    };
    iframe.src = 'http://b.com/test2.html';
    if (iframe.attachEvent) {
        iframe.attachEvent('onload', loadFunction);
    } else {
        iframe.onload  = loadFunction;
    }
    document.body.appendChild(iframe);
</script>

3) 獲取數據之後銷燬這個iframe,釋放內存.這也保證了安全性.(不被其餘域iframe 訪問)

<script type="text/javascript">
    iframe.contentWindow.document.write('');
    iframe.contentWindow.close();
    document.body.removeChild(iframe);
</script>

window.top方案

window.top方法能夠訪問最頂層的window對象,能夠取到最頂層window對象的屬性和方法。這樣子框架就能夠操做父頁面的交互了。window.parent能夠獲得父框架的window對象。

1) a頁面代碼

<script type="text/javascript">
    function funA(){
        console.log("a頁面的方法");
    }
    iframe = document.createElement('iframe');
    iframe.style.display = 'none';
    iframe.src = 'http://a.com:8080/b.html';
    document.body.appendChild(iframe);
</script>

2) b頁面的代碼

<script type="text/javascript">
console.log(window.top.funA());
function funB(){
  console.log("b頁面的方法");
}
iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = 'http://a.com:8080/c.html';
document.body.appendChild(iframe);
</script>

3) c頁面代碼

<script type="text/javascript">
  console.log(window.parent.funB());
</script>

cookie 和 WebSocket 協議跨域

cookie除了有exprise這個屬性以後還有path這個屬性,這個屬性是用來設置可訪問到cookie的路徑的,默認的是在當前cookie頁面的子目錄下是能夠訪問到的,可是默認的狀況下咱們是沒法再其餘父目錄下面訪問到這個cookie,這個時候咱們就能夠經過設置cookie來實現這個功能

如上面所說,可是在跨域之間的傳值要怎樣實現呢,其實cookie除了有path,還存在domain,domain這個屬性是能夠實現跨域的,可是必須保證這兩個域名有公共部分,何爲公共部分?就是像www.qq.com和www.sport.qq.com 這樣都有相同的qq.com的域名,具體的使用方法是把domain設置爲相同部分的域名,具體以下:

document.cookie = "name=value;expires=date;path=/;domain=qq.com"
另外在使用cookie的時候咱們還須要注意的是cookie的編碼問題,由於在cookie是不支持逗號、空格、分號的,因此在設置cookie的時候,須要使用escape()將輸入的信息進行轉碼,而後在要調用的時候使用unescape()來從新轉換回來.

WebSocket對象提供了用於建立和管理 WebSocket 鏈接,以及能夠經過該鏈接發送和接收數據的 API。

WebSocket構造器方法接受一個必須的參數和一個可選的參數:

WebSocket WebSocket(in DOMString url, in optional DOMString protocols);
WebSocket WebSocket(in DOMString url,in optional DOMString[] protocols);
url :表示要鏈接的URL。這個URL應該爲響應WebSocket的地址。
protocoles 能夠是一個單個的協議名字字符串或者包含多個協議名字字符串的數組。這些字符串用來表示子協議,這樣作可讓一個服務器實現多種WebSocket子協議(例如你可能但願經過制定不一樣的協議來處理不一樣類型的交互)。若是沒有制定這個參數,它會默認設爲一個空字符串。(可選)
構造器方法可能拋出如下異常:
SECURITY_ERR 試圖鏈接的端口被屏蔽。

WebSocket protocol是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通訊,同時容許跨域通信,是server push技術的一種很好的實現。
原生WebSocket API使用起來不太方便,咱們使用Socket.io,它很好地封裝了webSocket接口,提供了更簡單、靈活的接口,也對不支持webSocket的瀏覽器提供了向下兼容。具體可參閱前端常見跨域解決方案

參考文檔

H5新接口window.postMessage MDN
HTTP訪問控制CORS MDN
跨域資源共享CORS詳解
JavaScript跨域總結與解決辦法
詳解跨域(最全的解決方案)
淺談幾種跨域的方法
H5新接口webSocket MDN

相關文章
相關標籤/搜索