對前端跨域方案的認知總結

前端小然子發表於2019-04-10

什麼是跨域? -> 靈魂問題!!!

跨域呢 就是在一個域的文件或者指令碼試圖去請求另一個域的資源。


敲鍵盤、劃重點了啊

1、首先肯定是兩個域之間的通訊

2、重點要記住指令碼這兩個字

最近的一個專案中,做一個公用的後臺的殼子(多個後臺/域名),然後呢我團隊裡面的一個小美女做的時候發現一個問題:

在這個域名下請求同一個介面
這個專案內請求沒問題,然而這個殼子中請求的卻是無許可權訪問
複製程式碼

示例圖如下

跨域

這裡我們可以看到是a.com載入的b.com下的common.js

然後通過common.js請求的api.com返回的false

所以通過一個域下的指令碼去請求另一個域的資源也是跨域

我們通常說的跨域是由瀏覽器的同源策略限制的一類請求場景

說到這裡,不得不提一下 廣義的跨域同源策略


廣義的跨域

1.) 資源跳轉: A連結、重定向、表單提交
2.) 資源嵌入: <link>、<script>、<img>、<frame>等dom標籤,
    還有樣式中background:url()、@font-face()等檔案外鏈
3.) 指令碼請求: js發起的ajax請求、dom和js物件的跨域操作等
複製程式碼

同源策略

含義:

A網頁設定的Cookie, B網頁不能開啟,除非這兩個網頁“同源”

目的:

為了保證使用者資訊保安,防止惡意的網站竊取資料

同源策略是指三個相同:
  • 協議相同
  • 域名相同
  • 埠相同
同源策略的限制範圍
  • Cookie、LocalStorage、和IndexDB 無法獲取
  • DOM 無法獲得
  • AJAX 請求不能傳送

下面說一下怎麼突破跨域

  1. 通過jsonp跨域
  2. iframe跨域
  3. postMessage跨域
  4. CORS跨域
  5. Nginx代理跨域
  6. nodejs中介軟體代理跨域
  7. 利用WebSocket跨域

一、通過jsonp跨域

jsonp跨域的本質是通過動態的建立script,再請求一個帶引數的網址實現跨域通訊。

  1. 原生實現方法示例如下:
<script>
var src = "https://xxx.com/xxx.php?a=jsonpTest&callback=onback";

var script = document.createElement("script");
script.type = 'type/javascript';
script.src = src;

document.head.appendChild(script);

// 定義回撥函式
function onBack(res) {
    console.log(res);
}
<script>
複製程式碼

服務端返回:

onBack({state: true})
複製程式碼
  1. 通過jquery ajax實現:
$.ajax({
    url: 'https://xxx.com/xxx.php',
    type: 'get',
    dataType: 'jsonp',  // 請求方式為jsonp
    jsonpCallback: "onBack",    // 自定義回撥函式名
    data: {a: jsonpTest},
    success: function () {
        console.log(res);
    }
});
複製程式碼

其他框架的方法原理一致 可根據不同的request模組自行調整

後端php程式碼示例:

<?php
$callback = $_GET['callback'];
if (empty($callback)) {
    echo json_encode([
        state => false,
        errMsg => "請求方式不正確"
    ]);
    return;
}
$data = json_encode([
    state => true,
    errMsg => ""
]);

echo "{$callback}({$data})";
複製程式碼

二、iframe跨域

1. document.domain + iframe跨域

僅在主域相同,子域不同的情況下有效
複製程式碼

實現原理:兩個頁面都通過js強制設定document.domain為基礎主域,也就實現了同域。

1.) 父頁面(n.xxx.com/a.html)

<iframe id="iframe" src="http://m.xxx.com/b.html"></iframe>
<script>
    document.domain = 'xxx.com';
    var user = 'admin';
</script>
複製程式碼

2.) 子頁面(m.xxx.com/b.html)

<script>
    document.domain = 'xxx.com';
    // 獲取父視窗中變數
    console.log(window.parent.user);
</script>
複製程式碼

2. location.hash + iframe跨域

實現原理:增加中間頁面來實現。三個頁面,不同域之間利用iframe的location.hash傳值,相同域之間直接js通訊。

具體實現如圖:

對前端跨域方案的認知總結

主要方法 onhashchange

程式碼實現:

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

    // 向b.html傳hashsetTimeout(function() {
        iframe.src = iframe.src + '#user=admin';
    }, 1000);
    
    // 開放給同域c.html的回撥方法
    function onCallback(res) {
        console.log(res);
    }
</script>
複製程式碼
  1. b.html
<iframe id="iframe" src="http://www.a.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>
複製程式碼
  1. c.html
<script>
    // 監聽b.html傳來的hash值
    window.onhashchange = function () {
        // 通過操作同域a.html的js回撥,將結果傳回去
        window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
    };
</script>
複製程式碼

3. window.name + iframe跨域

window.name屬性的特性:

  • name值在不同的頁面(甚至不同域名)載入後依舊存在
  • 可以支援非常長的 name 值(2MB)

1)a.html

<script>
var proxy = function(url, callback) {
    var state = 0;
    var iframe = document.createElement("iframe");
    iframe.src = url;
    
    iframe.onload = function() {
        if (state === 0) {
            // 第一次onload成功後 切換到代理頁面
            iframe.contentWindow.location = 'http://xxx.com/proxy.html';
            state = 1;
        } else if (state === 1) {
            // 第二次onload成功後  讀取資料 並銷燬這個iframe
            // 1.釋放記憶體
            // 2.保證不被其他域frame js訪問
            callback(iframe.contentWindow.name);
            destoryFrame();
        }
    }
    
    document.body.appendChild(iframe);

    // 銷燬這個iframe
    function destoryFrame() {
        iframe.contentWindow.document.write('');
        iframe.contentWindow.close();
        document.body.removeChild(iframe);
    }
}

// 請求跨域b頁面資料
proxy('http://yyy.com/b.html', function(data){
    console.log(data);
});


</script>
複製程式碼

2)b.html

<script>
    window.name = '在這裡填寫跨域資料';
</script>
複製程式碼

3)proxy.html 代理頁,要求與a.html同域,內容為空即可。

通過iframe的src屬性從其他域轉向本域,跨域資料就從iframe的window.name由其他域傳遞到本域。

一波操作666,完美繞過了瀏覽器的跨域訪問限制,但是同時又是安全操作。

在阿里雲看到過相關操作

三。postMessage跨域

postMessage是HTML5 level2裡面的API,可以用來解決以下問題:

  • 頁面和這個頁面開啟的新視窗的訊息傳遞
  • 多個視窗之間的訊息傳遞
  • 頁面與巢狀的iframe之間的訊息傳遞

postMessage(data, origin)

data:

html5規範支援任意基本型別或可複製的物件,但部分瀏覽器只支援字串,所以傳參時最好用JSON.stringify()序列化。

origin:

協議+主機+埠號,也可以設定為"*",表示可以傳遞給任意視窗,如果要指定和當前視窗同源的話設定為"/"。

示例如下:

a.html

<iframe id="iframe" src="http://yyy.com/b.html" style="display:none;"></iframe>
<script>       
    var iframe = document.getElementById('iframe');
    iframe.onload = function() {
        var data = {
            name: '我是xxx'
        };
        // 向yyy.com傳送跨域資料
        iframe.contentWindow.postMessage(JSON.stringify(data), 'http://yyy.com');
    };

    // 接受yyy.com返回的資料
    window.addEventListener('message', function(e) {
        console.log(e.data);
    }, false);
</script>
複製程式碼

b.html

<script>
    // 接收xxx.com的資料
    window.addEventListener('message', function(e) {
        console.log(e.data);

        var data = JSON.parse(e.data);
        if (data) {
            data.name2 = "我是yyyy";

            // 處理後再發回xxx.com
            window.parent.postMessage(JSON.stringify(data), 'http://xxx.com');
        }
    }, false);
</script>
複製程式碼

四、CORS跨域

普通跨域請求:只服務端設定Access-Control-Allow-Origin即可,前端無須設定,若要帶cookie請求:前後端都需要設定。

注:由於同源策略的限制,所讀取的cookie為跨域請求介面所在域的cookie,而非當前頁。

所有瀏覽器都支援該功能(IE8+:IE8/9需要使用XDomainRequest物件來支援CORS)),CORS也已經成為常用的跨域解決方案。

1.通過前端設定

// 攜帶cookie
xhr.withCredentials = true;
複製程式碼

2.服務端配置

設定header頭


// 允許跨域訪問的域名,若有埠需寫全(協議+域名+埠)
// 若沒有埠末尾不用加'/'
Access-Control-Allow-Origin *

// 允許前端帶認證cookie
// 開啟這個以後上面的不能使用萬用字元 必須指定具體域名
Access-Control-Allow-Credentials true

// 提示OPTIONS預檢的時候,後端需要設定的兩個常用頭
Access-Control-Allow-Headers Content-Type,X-Requested-With

複製程式碼

五、Nginx代理跨域

1)CORS的一種

示例:

location / {
  add_header Access-Control-Allow-Origin *;
}
複製程式碼

其他配置與上述CORS類似

2)反向代理介面 通過nginx配置一個代理伺服器做跳板機,反向代理訪問介面,並修改cookie中的domain資訊,方便當前域的cookie寫入,實現跨域登入。

這波操作可以呀,老鐵

六、nodejs中介軟體代理跨域

原理和nginx相同,通過代理伺服器,實現資料的轉發,也可以通過設定cookieDomainRewrite引數修改響應頭中cookie的域名,實現當前域的cookie寫入。

七、利用WebSocket跨域

WebSocket 是H5的一種新協議,實現了瀏覽器和伺服器的全雙工通訊,同時允許跨域通訊。

通常我們會使用socket.io來簡化操作;

直接建立ws/wss連結就行了

相關文章