解決跨域問題的8種方式


 

一、同源策略
 同源策略(Same origin policy)是由 NetScape 公司在1995年提出的一種安全策略;同源策略只是一個約定,提出后被各個瀏覽器廠商采納,並以各自的方式實現了同源策略。
 
 同源策略:是瀏覽器最核心也最基本的安全功能,會約束瀏覽器的行為;
 同源策略會限制瀏覽器:只允許本域內的腳本讀寫本域內的資源,不允許訪問本域外的資源。

判斷是否同源?
 判斷要素有三:協議、域名、端口號;
 (三者全部一致才視為同源,即屬於同一個域;否則視為非同源。)

限制范圍
 無法共享 cookie, localStorage, indexDB。
 無法操作彼此的 dom 元素。
 無法發送 ajax 請求。

二、跨域


 域(Domain)是Windows網絡中獨立運行的單位;
 域之間相互訪問則需要建立信任關系,信任關系是連接在域與域之間的橋梁;
 當一個域與其他域建立了信任關系后,2個域之間不但可以按需要相互進行管理,還可以跨網分配文件和打印機等設備資源,使不同的域之間實現網絡資源的共享與管理.


跨域:突破同源策略的限制,在兩個不同的域之間(非同源頁面)實現資源交互。

跨域分類:
 1)使用 Ajax 引發的跨域問題
當調用 Ajax 時:調用 Ajax 發送請求的頁面 所在的域,和被請求頁面所在的域不一致
 2)類似頁面嵌入 ifream 引起的跨域問題
當操作 ifream 內引入的元素時:ifream 所屬頁面的域,和 ifream 引入頁面的域不一致


三、跨域實現方法?

1. JSONP:
 JSONP 就是跨域的一種實現方式。(JSONP 全稱 JSON with Padding,是數據格式 JSON 的一種 “使用模式”,可用於解決主流瀏覽器的跨域數據訪問的問題。)
 JSONP 是一種非正式傳輸協議,該協議的一個要點就是允許用戶傳遞一個 callback 參數給服務端,然后服務端返回數據時會將這個 callback 參數作為函數名來包裹住 JSON 數據,這樣客戶端就可以隨意定制自己的函數來自動處理返回數據了。

JSONP 的原理:
 利用 script 標簽不受同源策略影響,可以跨域引入外部資源的特性,讓服務器端返回可執行的 JS 函數,將要返回的數據作為參數傳進函數,以此實現跨域加載數據的目的
 此時,繞過 Ajax,並未使用它,但同樣達成了請求數據的目的

[](【備注】——script 標簽引用資源得本質是:
 1)向 src 發送請求
 2)將資源下載到當前頁面
 3)當資源加載完畢后,把該資源當做 JS 代碼來立刻執行——【備注】)

JSONP 的使用:

 1、動態創建 script 標簽,src 地址指向數據接口,並傳遞 callback 參數
 2、定義數據處理函數
 3、服務端接收請求,解析參數,計算數,返回回調函數字符串
 4、將回調函數字符串引入頁面並作為 JS 去執行:此時會調用數據處理函數,數據會作為數據處理函數的參數被處理計算出一個結果


JSONP 的優缺點
 優點
 1)因 script 隸屬於 HTML 的標簽,所以不存在兼容問題

 缺點
 1)因需使用 URL 引入資源,所以 JSONP 僅支持 get 請求
 2)因 script 標簽會將資源作為 JS 代碼執行,所以可能會被注入惡意代碼


2、CORS
 CORS 全稱 Cross-Origin Resource Sharing,即:跨來源資源共享。它是一份瀏覽器技術的規范,提供了Web服務從不同網域傳來沙盒腳本的方法,以避開瀏覽器的同源策略,是JSONP模式的現代版


CORS 的原理:
 1、當使用 XMLHttpRequest 發送請求時,如果瀏覽器發現該請求不符合同源策略,會給該請求加一個請求頭:Origin;
 2、后台進行一系列處理,如果確定接受請求則在返回結果中加入一個響應頭:Access-Control-Allow-Origin;
 3、瀏覽器判斷該響應頭中是否包含 Origin 的值:
 4、如果包含瀏覽器則會處理響應,前端就可以拿到響應數據;
 5、如果不包含瀏覽器直接駁回,此時前端無法拿到響應數據。
 (簡單來說,就是瀏覽器匹配請求頭和響應頭,如果符合要求便可拿到數據,否則無法拿到數據。整個過程都是由瀏覽器自動完成。這就像一個白名單,代表着誰可以拿到數據。)


CORS 的使用:
 前端:正常使用 AJAX 發送請求
 服務端:若確定接受請求,則在返回結果中加入響應頭:Access-Control-Allow-Origin


CORS 的優缺點:
 優點
 1)使用簡單方便、更為安全
 2)支持 POST 請求方式

 缺點
 1)CORS 是一種新型跨域問題的解決方案:存在兼容問題——僅支持 IE10 以上

3、降域
 降域仍是解決跨域問題的一種方案,通過雙向設置 document.domain 的值,解決主域名下的跨域問題


降域的原理
 比如,有兩個二級域名:a.yang.com 和 b.yang.com,可通過設定 document.domain 的值為主域名:yang.com 的方式,突破瀏覽器的同源策略限制,來獲取和操作對方的元素

 這就好比,小 A 和小 B,手里拿着城主的令牌,通過哨卡時才能暢行無阻,否則哨卡不讓過

 這也就決定了,降域具有很大的局限性,適用范圍較小,適合在同一主域名下使用;需要有降的空間,方可使用降域方式

降域的使用
 A 頁面域為:a.yang.com

 B 頁面域為:b.yang.com

 A 和 B 兩頁面都需加入該行代碼:document.domain = 'yang.com'; ,‘yang.com 是 a.yang.com 和 b.yang.com 的主域名

4、postMessage
 那么當兩個主域名完全不同時,應該如何處理呢?來看看新方法 postMessage。

 postMessage 原理
 postMessage 是 HTML5 中新增方法,可實現跨域通信;

 postMessage 並不是向服務器讀寫資源,只是向外發送消息而已;可以把它當做使用手機發送短信消息,僅此而已。

 也就是:A 頁面向 B 頁面發送了一條消息,B 頁面會接受到該消息,如果 B 頁面需要該消息,則監聽 message;否則無需關心該消息

 postMessage 的使用
 發送方:為目標元素添加事件處理程序,監聽事件類型

 接收方:為 window 添加事件處理程序,事件類型為 messag


postMessage 的注意點
 這樣就解除了降域的限制,可以將 postMessage 應用到各個不同的域之間,實現跨域訪問。該方式比較安全,因為對方並不能直接操控我方資源,僅僅是發了一條消息,相當於指令,而最終操作權限仍在自己手中
 雖然解決了降域的限制,但是:postMessage 是 window 的一個方法,話句話說:它的弱點是只能向 window 窗口發送消息。所以使用時至少要有一個 window 才行,postMessage 不可以向其他域名發送消息

(注意) 
 最后:降域和 postMessage 都是小眾的降域方式,並不是經常使用,若有需求可按需選擇


5、nginx代理跨域(服務器代理)

 跨域原理: 同源策略是瀏覽器的安全策略,不是HTTP協議的一部分。服務器端調用HTTP接口只是使用HTTP協議,不會執行JS腳本,不需要同源策略,也就不存在跨越問題。

 實現思路:通過nginx配置一個代理服務器(域名與domain1相同,端口不同)做跳板機,反向代理訪問domain2接口,並且可以順便修改cookie中domain信息,方便當前域cookie寫入,實現跨域登錄。

#proxy服務器
server {
    listen       81;
    server_name  www.domain1.com;

    location / {
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
        index  index.html index.htm;

        # 當用webpack-dev-server等中間件代理接口訪問nignx時,此時無瀏覽器參與,故沒有同源限制,下面的跨域配置可不啟用
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #當前端只跨域不帶cookie時,可為*
        add_header Access-Control-Allow-Credentials true;
    }
}

6、 WebSocket協議跨域

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

 1.)前端代碼:
<script>
var socket = io('http://www.domain2.com:8080');

// 連接成功處理
socket.on('connect', function() {
    // 監聽服務端消息
    socket.on('message', function(msg) {
        console.log('data from server: ---> ' + msg);
    });

    // 監聽服務端關閉
    socket.on('disconnect', function() {
        console.log('Server socket has closed.');
    });
});

document.getElementsByTagName('input')[0].onblur = function() {
    socket.send(this.value);
};
</script>

Nodejs socket后台:
var http = require('http');
var socket = require('socket.io');

// 啟http服務
var server = http.createServer(function(req, res) {
    res.writeHead(200, {
        'Content-type': 'text/html'
    });
    res.end();
});

server.listen('8080');
console.log('Server is running at port 8080...');

// 監聽socket連接
socket.listen(server).on('connection', function(client) {
    // 接收信息
    client.on('message', function(msg) {
        client.send('hello:' + msg);
        console.log('data from client: ---> ' + msg);
    });

    // 斷開處理
    client.on('disconnect', function() {
        console.log('Client socket has closed.');
    });
});


7、location.hash + iframe跨域

實現原理: a欲與b跨域相互通信,通過中間頁c來實現。 三個頁面,不同域之間利用iframe的location.hash傳值,相同域之間直接js訪問來通信。

具體實現:A域:a.html -> B域:b.html -> A域:c.html,a與b不同域只能通過hash值單向通信,b與c也不同域也只能單向通信,但c與a同域,所以c可通過parent.parent訪問a頁面所有對象。

1.)a.html:(http://www.domain1.com/a.html)

<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');

    // 向b.html傳hash值
    setTimeout(function() {
        iframe.src = iframe.src + '#user=admin';
    }, 1000);
   
    // 開放給同域c.html的回調方法
    function onCallback(res) {
        alert('data from c.html ---> ' + res);
    }
</script>

2.)b.html:(http://www.domain2.com/b.html)

<iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');

    // 監聽a.html傳來的hash值,再傳給c.html
    window.onhashchange = function () {
        iframe.src = iframe.src + location.hash;
    };
</script>

3.)c.html:(http://www.domain1.com/c.html)

<script>
    // 監聽b.html傳來的hash值
    window.onhashchange = function () {
        // 再通過操作同域a.html的js回調,將結果傳回
        window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
    };
</script>


8、window.name + iframe跨域
window.name屬性的獨特之處:name值在不同的頁面(甚至不同域名)加載后依舊存在,並且可以支持非常長的 name 值(2MB)。

1.)a.html:(http://www.domain1.com/a.html)

var proxy = function(url, callback) {
    var state = 0;
    var iframe = document.createElement('iframe');

    // 加載跨域頁面
    iframe.src = url;

    // onload事件會觸發2次,第1次加載跨域頁,並留存數據於window.name
    iframe.onload = function() {
        if (state === 1) {
            // 第2次onload(同域proxy頁)成功后,讀取同域window.name中數據
            callback(iframe.contentWindow.name);
            destoryFrame();

        } else if (state === 0) {
            // 第1次onload(跨域頁)成功后,切換到同域代理頁面
            iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
            state = 1;
        }
    };

    document.body.appendChild(iframe);

    // 獲取數據以后銷毀這個iframe,釋放內存;這也保證了安全(不被其他域frame js訪問)
    function destoryFrame() {
        iframe.contentWindow.document.write('');
        iframe.contentWindow.close();
        document.body.removeChild(iframe);
    }
};

// 請求跨域b頁面數據
proxy('http://www.domain2.com/b.html', function(data){
    alert(data);
});
2.)proxy.html:(http://www.domain1.com/proxy....
中間代理頁,與a.html同域,內容為空即可。

3.)b.html:(http://www.domain2.com/b.html)

<script>
    window.name = 'This is domain2 data!';
</script>
總結:通過iframe的src屬性由外域轉向本地域,跨域數據即由iframe的window.name從外域傳遞到本地域。這個就巧妙地繞過了瀏覽器的跨域訪問限制,但同時它又是安全操作。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM