跨域以及解決方案匯總

跨域

是指一個域下面的文檔或者腳本視圖去請求另一個域下的資源

  1. 資源跳轉(zhuǎn):A鏈接,重定向,表單提交
  2. 資源嵌入:<link> , <script> , <img> , <frame>dom 標(biāo)簽, 還有樣式中的background: url(), @font-face() 等文件外鏈
  3. 腳本請求: js 發(fā)起的 ajax 請求, domjs 對象的跨域請求

同源策略

由NetScape公司1995年引入瀏覽器,是瀏覽器最核心也最基本的安全功能。如果沒有這個功能,瀏覽器很容易受到XSS,CSFR等攻擊。同源即 協(xié)議 + 域名 + 端口 三者相同,即使兩個域名指向同一個ip地址,也不是同源。

同源策略限制一下幾種行為

  1. Cookie, LocalStorageIndexDB 無法讀取
  2. DOMJS 對象無法獲得
  3. AJAX 請求不能發(fā)送

跨域解決方案

  1. 通過 JSONP 跨域

    JSONP 只能get請求

     <script src="http://www.b.com/request?callback=jsonPCallBack">
         function jsonPCallBack(content) {...}
        </script>
    
    $.ajax("http://www.b.com/request", {
      jsonpCallback: "moty",
      dataType: "jsonp",
      success: function(json) {...}
    });
    
  2. document.domain + iframe 跨域

    a. 這兩個域名必須屬于同一個一級域名!而且所用的協(xié)議,端口都要一致,否則無法利用document.domain進行跨域。

    b. 如果是同一級域名下的子域名,如:m.a.com, api.a.com, 需要在 javascript 里面設(shè)置 domain 才行

    document.domain = 'a.com';
    

    news.baidu.com下的news.html頁面:

    <script>
        document.domain = 'baidu.com';
        var ifr = document.createElement('iframe');
        ifr.src = 'map.baidu.com/map.html';
        ifr.style.display = 'none';
        document.body.appendChild(ifr);
        ifr.onload = function(){
            var doc = ifr.contentDocument || ifr.contentWindow.document;
            // 這里可以操作map.baidu.com下的map.html頁面
            var oUl = doc.getElementById('ul1');
            alert(oUl.innerHTML);
            ifr.onload = null;
        };
    </script>
    

    map.baidu.com下的map.html頁面:

    <ul id="ul1">我是map.baidu.com中的ul</ul>
    <script>
     document.domain = 'baidu.com';
    </script>
    
  3. location.hash + iframe

    原理是利用location.hash來進行傳值。

    在url: http://a.com#helloword中的‘#helloworld’就是location.hash,改變hash并不會導(dǎo)致頁面刷新,所以可以利用hash值來進行數(shù)據(jù)傳遞,當(dāng)然數(shù)據(jù)容量是有限的。

    假設(shè)域名a.com下的文件cs1.html要和cnblogs.com域名下的cs2.html傳遞信息,cs1.html首先創(chuàng)建自動創(chuàng)建一個隱藏的iframe,iframe的src指向cnblogs.com域名下的cs2.html頁面,這時的hash值可以做參數(shù)傳遞用。

    cs2.html響應(yīng)請求后再將通過修改cs1.html的hash值來傳遞數(shù)據(jù)(由于兩個頁面不在同一個域下IE、Chrome不允許修改parent.location.hash的值,所以要借助于a.com域名下的一個代理iframe;Firefox可以修改)。

    同時在cs1.html上加一個定時器,隔一段時間來判斷l(xiāng)ocation.hash的值有沒有變化,一點有變化則獲取獲取hash值。

    a.html下的文件cs1.html

    function startRequest(){
        var ifr = document.createElement('iframe');
        ifr.style.display = 'none';
        ifr.src = 'http://www.cnblogs.com/lab/cscript/cs2.html#paramdo';
        document.body.appendChild(ifr);
    }
     
    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);
    

    cnblogs.com下的cs2.html

    //模擬一個簡單的參數(shù)處理操作
    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,
            // 所以要利用一個中間的cnblogs域下的代理iframe
            var ifrproxy = document.createElement('iframe');
            ifrproxy.style.display = 'none';
             // 注意該文件在"a.com"域下
            ifrproxy.src = 'http://a.com/test/cscript/cs3.html#somedata';   
            document.body.appendChild(ifrproxy);
        }
    }
    

    a.html下的文件cs3.html

    //因為parent.parent和自身屬于同一個域,所以可以改變其location.hash的值
    parent.parent.location.hash = self.location.hash.substring(1);
    
  4. window.name + iframe

    window.name (一般在js代碼里出現(xiàn))的值不是一個普通的全局變量,而是當(dāng)前窗口的名字,這里要注意的是每個iframe都有包裹它的window,而這個window是top window的子窗口,而它自然也有window.name的屬性, window.name屬性的神奇之處在于name 值在不同的頁面(甚至不同域名)加載后依舊存在(如果沒修改則值不會變化),并且可以支持非常長的 name 值(2MB)。

    window.name = 'abc';
    window.name; // abc
    window.location = 'http://www.baidu.com';
    window.name; // abc
    

    a.html, proxy.html 同屬于一個域下,b.html是另一個域的

    b.html內(nèi)容

    <script>
     window.name = 'b.html\'s data';
    </script>
    

    a.html

    <script type="text/javascript">
    var otherLoaded = false,
    iframe = document.createElement('iframe'),
    loadfn = function() {
        if (otherLoaded) {
            var data = iframe.contentWindow.name; // 讀取數(shù)據(jù)
            alert(data); // 彈出b.html's data
         // 清理工作
            iframe.contentWindow.document.write('');
            iframe.contentWindow.close();
            document.body.removeChild(iframe);
        } else if (!otherLoaded) {
            otherLoaded = true;
            // 設(shè)置的代理文件
            iframe.contentWindow.location = "http://localhost:8001/proxy.html"; 
        }
    };
    iframe.src = 'http://localhost:8002/b.html';
    if (iframe.attachEvent) {
        iframe.attachEvent('onload', loadfn);
    } else {
        iframe.onload = loadfn;
    }
    document.body.appendChild(iframe);
    </script>
    

    可以看到, 第一次設(shè)置iframe的地址為b.html, 這樣的話b.html會被加載進來,但是并不能直接訪問iframe.contentWindow.name, 因為a.html和b.html目前不同源,如果將loadfn的實現(xiàn)改為var data = iframe.contentWindow.name;,會出來這個錯誤:

    a.html: 
    Uncaught DOMException: Blocked a frame with origin "http://localhost:8001" from accessing a cross-origin frame.
    

    那怎么辦呢, 既然不同源, 就改成同源唄, 所以將iframe地址改成與a.html同源的proxy.html,由于window.name在地址變化時值不變, 所以iframe.contentWindow.name的值還是之前的值, 也就是b.html窗口的值, 而又滿足的同源的要求, 所以可以訪問成功。

  5. postMessage

    window.postMessage的功能是允許程序員跨域在兩個窗口/frames間發(fā)送數(shù)據(jù)信息。基本上,它就像是跨域的AJAX,但不是瀏覽器跟服務(wù)器之間交互,而是在兩個客戶端之間通信。安全著想,接受消息的時候應(yīng)該檢驗來源,也就是event.origin或者event.source。

    a.html

    <h1 class="header">page A</h1>
    <div class="mb20">
        <textarea name="ta" id="data" cols="30" rows="5">hello world</textarea>
        <button style="font-size:20px;" onclick="send()">post message</button>
    </div>
    <iframe src="http://localhost:9022/b.html" id="child" 
    

style="display: block; border: 1px dashed #ccc; height: 300px;"></iframe>

<script>
function send() {
var data = document.querySelector('#data').value;
// 觸發(fā)跨域子頁面的messag事件
window.frames[0].postMessage(data, 'http://localhost:9022/');
}

   window.addEventListener('message', function(messageEvent) {
       if (messageEvent.origin !== 'http://localhost:9022/') return
    var data = messageEvent.data; 
    console.info('message from child:', data);
}, false);

</script>


b.html

```html
<h1 class="header">page B</h1>

<input type="text" id="inp" value="some contents..">
<button onclick="send()">send</button>
<script>
window.addEventListener('message', function(ev) {
    if (ev.source !== window.parent) {return;}
    var data = ev.data;
    console.info('message from parent:', data);
}, false);

function send() {
    var data = document.querySelector('#inp').value;
    // 若父頁面的域名和指定的不一致,則postMessage失敗
    window.parent.postMessage(data, 'http://localhost:9011/'); 
    // 觸發(fā)父頁面的message事件
    // parent.postMessage(data, '*'); 
}
</script>
  1. 跨域資源共享(CORS)

    CORS 通過新增一系列的HTTP頭,讓服務(wù)器能聲明那些來源能訪問該服務(wù)器上的資源,GET以外的請求,會以O(shè)PTIONS請求方式發(fā)一個預(yù)請求,從而得知服務(wù)器對資源請求支持的HTTP方法,在確認(rèn)服務(wù)器允許跨域請求資源的情況下,以實際的HTTP請求方法發(fā)送真正的請求。

    1. 請求頭:

      Origin :

      普通的HTTP請求也會帶有,在CORS中專門作為Origin信息供后端比對,表明來源域。

      Access-Control-Request-Method :

      接下來請求的方法,例如PUT, DELETE等等

      Access-Control-Request-Headers :

      自定義的頭部,所有用setRequestHeader方法設(shè)置的頭部都將會以逗號隔開的形式包含在這個頭中

    2. http響應(yīng)頭

      然后瀏覽器再根據(jù)服務(wù)器的返回值判斷是否發(fā)送非簡單請求。簡單請求前面講過是直接發(fā)送,只是多加一個origin字段表明跨域請求的來源。然后服務(wù)器處理完請求之后,會再返回結(jié)果中加上如下控制字段

      Access-Control-Allow-Origin :

      允許跨域訪問的域,可以是一個域的列表,也可以是通配符"*"。這里要注意Origin規(guī)則只對域名有效,并不會對子目錄有效。即http://foo.example/subdir/ 是無效的。但是不同子域名需要分開設(shè)置,這里的規(guī)則可以參照同源策略

      Access-Control-Allow-Credentials :

      是否允許請求帶有驗證信息,XMLHttpRequest請求的withCredentials標(biāo)志設(shè)置為true時,認(rèn)證通過,瀏覽器才將數(shù)據(jù)給腳本程序。

      Access-Control-Expose-Headers :

      允許腳本訪問的返回頭,請求成功后,腳本可以在XMLHttpRequest中訪問這些頭的信息

      Access-Control-Max-Age :

      緩存此次請求的秒數(shù)。在這個時間范圍內(nèi),所有同類型的請求都將不再發(fā)送預(yù)檢請求而是直接使用此次返回的頭作為判斷依據(jù),非常有用,大幅優(yōu)化請求次數(shù)

      Access-Control-Allow-Methods :

      允許使用的請求方法,以逗號隔開

      Access-Control-Allow-Headers :

      自定義的頭部,以逗號隔開,大小寫不敏感

  2. nginx代理跨域

  3. nodejs中間件代理跨域

  4. websocket協(xié)議跨域

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 大家好,我是IT修真院鄭州分院第一期的學(xué)員胡嘉杰,一枚正直純潔善良的WEB前端程序員。 今天給大家分享一下,修真院...
    ithinker5閱讀 547評論 0 1
  • 1. 什么是跨域? 跨域一詞從字面意思看,就是跨域名嘛,但實際上跨域的范圍絕對不止那么狹隘。具體概念如下:只要協(xié)議...
    w_zhuan閱讀 622評論 0 0
  • JavaScript跨域總結(jié)與解決辦法 什么是跨域 1、document.domain+iframe的設(shè)置 2、動...
    tom_123閱讀 525評論 0 1
  • 1. 什么是跨域? 跨域一詞從字面意思看,就是跨域名嘛,但實際上跨域的范圍絕對不止那么狹隘。具體概念如下:只要協(xié)議...
    他在發(fā)呆閱讀 861評論 0 0
  • 什么是跨域 跨域,是指瀏覽器不能執(zhí)行其他網(wǎng)站的腳本。它是由瀏覽器的同源策略造成的,是瀏覽器對JavaScript實...
    他方l閱讀 1,139評論 0 2

友情鏈接更多精彩內(nèi)容