Web 學習之跨域問題及解決方案

熊建剛發表於2019-02-28

在做前端開發時,我們時常使用ajax與伺服器通訊獲取資源,享受ajax便利的同時,也知道它有限制:跨域安全限制,即同源策略。

同源策略(SOP),核心是確保不同源提供的檔案之間是相互獨立的

預設情況下,XHR物件只能訪問與包含它的頁面處於同一域中的資源,這種限制可以預防某些惡意攻擊,但同時也帶來很多不便。

本篇對於常見的解決瀏覽器跨域問題的方案進行總結闡述。

常見解決跨域問題的方案

在web開方中,解決跨域問題最常見的方法有:

  • document.domain+iframe(子域名代理)
  • jsonp實現跨越
  • postMessage實現跨越
  • CORS服務端解決跨域

接下來對以上常用方式進行詳細闡述:

1.document.domain + iframe(子域名代理)

對於主域相同而子域不同的情況,可以通過設定document.domain的辦法來解決。如:對於兩個檔案www.a.com/a.html和http… = `a.com`;然後在a.html檔案中建立一個iframe,通過iframe兩個js檔案即可互動資料:


    //www.a.com/a.html
    <script>
        document.doamin = `a.com`;
        var iframe = document.createElement(`iframe`);
        iframe.src = `http://blog.a.com/b.html`;
        document.body.appendChild(iframe);
        iframe.onload = function() {
            var doc = iframe.contentDocument || iframe.contentWindow.document;
            // 在這裡操縱b.html
            console.log(doc);
        };
    </script>複製程式碼

在blog.a.com/b.html內編寫程式碼:


    //blog.a.com/b.html
    <script>
        document.domain = `a.com`;
    </script>複製程式碼

備註:某一頁面的domain預設等於window.location.hostname。主域名是不帶www的域名,例如a.com,主域名前
面帶字首的通常都為二級域名或多級域名,例如blog.a.com其實是二級域名。 domain只能設定為主域名,不可以在
blog.a.com中將domain設定為test.a.com。

問題:
1、安全性,當一個站點(b.a.com)被攻擊後,另一個站點(c.a.com)會引起安全漏洞。
2、如果一個頁面中引入多個iframe,要想能夠操作所有iframe,必須都得設定相同domain。

2.jsonp實現跨域

JSONP,即帶填充的JSON,可以在JavaScript中繞過同源策略,發起跨域HTTP請求。

JSONP,一個無關標準,使用指令碼標籤來跨域獲取資料的技術。

同源策略有一個顯著例外:HTML指令碼元素是可以規避SOP檢查的。這意味著我們可以通過載入外部JavaScript檔案的方式來向其他源發出HTTP請求。

複製程式碼
//http://blog-resource.bj.bcebos.com/files/2016/03/info.js
var jsonpData = {
    name: `jsonp`,
    title: `JSONP data`
};複製程式碼

- 動態的回撥函式

JSONP通過script元素載入JSON,通過指令碼URL的查詢字串,伺服器將響應封裝在回撥函式中,而回撥函式名由請求者在URL查詢字串中給出,此回撥函式,即填充。填充可以是任何有效的JavaScript表示式,最常見的是變數和回撥函式。複製程式碼
<script>
    var jsonpCallback = function(data) {
        console.log(`The response data: ` + JSON.stringify(data));
    };
    var script = document.createElement(`script`);
    script.async = true;
    script.src = `http://blog-resource.bj.bcebos.com/files/2016/03/info2.js?callback=jsonpCallback`;
    document.body.appendChild(script);
</script>複製程式碼
在全域性作用域下定義回撥函式,當HTML DOM中加入script標籤時發起請求,服務端可以通過URL獲取回撥函式名,並生成返回如下類似內容:複製程式碼
jsonpCallback({
    name: `jsonp`,
    title: `JSONP data`
});複製程式碼
如php實現:複製程式碼
<?php
    header("Content-type: application/javascript");
    $callback = $_GET[`callback`];
    $data = json_encode(array(
        `name` => `jsonp,
        `title` => `JSONP data`
    ), JSON_PRETTY_PRINT);
    echo "$callback($data);";
?>複製程式碼
複製程式碼
$.ajax({    
       url: ``,  // 跨域URL   
       type: `GET`,    
       dataType: `jsonp`,    
       jsonp: `jsonpcallback`, //預設callback   
       data: mydata, //請求資料   
       timeout: 5000, 
       success: function (json) { //客戶端jquery預先定義好的callback函式,成功獲取跨域伺服器上的
json資料後,會動態執行這個callback函式    
           if(json.actionErrors.length!=0){    
               alert(json.actionErrors);    
           }    
       },    
       complete: function(XMLHttpRequest, textStatus){    

       }
   });複製程式碼

### 3.伺服器端的跨域解決方案

跨域資源共享(CORS)定義了瀏覽器和伺服器如何通過可控方式進行跨域通訊。CORS通過新增特殊HTTP頭資訊以允許瀏覽器和伺服器判斷請求是成功還是失敗。幾乎所有現代瀏覽器都支援CORS。

- HTTP請求

當跨域傳送HTTP請求時,支援CORS的瀏覽器會通過,新增額外Origin頭資訊指定請求源,其值包括請求協議、域名、埠。如:

```Origin: http://www.codingplayboy.com複製程式碼

若請求頭中無Origin資訊,伺服器將不反悔任何CORS頭資訊。

服務端接收到請求時會檢查頭資訊確定是否接受請求:若接受請 求,必須返回一個包含Access-Control-Allow-Origin響應頭,其值與請求頭Origin值相同,如:

Access-Control-Allow-Origin: http://www.codingplayboy.com

對於公共資源且允許任何源請求資料,伺服器通常返回該值為萬用字元*,如:

`Access-Control-Allow-Origin: *

瀏覽器接收到伺服器HTTP響應時,首先會檢查Access-Control-Allow-Origin的值,只有當值存在且同Origin匹配,才繼續處理該請求。

下面是一段使用CORS跨域請求的程式碼:


    function createCORS(url, method) {
        if (typeof XMLHttpRequest === `undefined`) {
            return null;
        }
        //標準瀏覽器
        var xhr = new XMLHttpRequest();
        if (`withCredentials` in xhr) {
            xhr.open(method, url, true);
        }else if (typeof XDomainRequest !== `undefined`) {
            //IE
            xhr = new XDomainRequest();
            xhr.open(method, null);
        }else {
            //不支援CORS
            xhr = null;
        }
        return xhr;
    }
    var req = createCORS(`http://blog.codingplayboy.com`, `GET`);
    if (req) {
        req.onload = function() {

        };
        req.send();
    }複製程式碼

Cookie及HTTP認證頭
我們還需要知道不同於普通的HTTP請求,使用CORS傳送請求時,預設情況下,瀏覽器不會攜帶任何Cookie和HTTP認證頭等識別資訊,只有當我們將XMLHttpRequest物件的withCredentials屬性值設為true,才在該請求傳送時新增額外識別資訊。


    var xhr = new XMLHttpRequest();
    xhr.withCredentials = true;複製程式碼

若伺服器需要識別資訊,則在響應頭中新增Access-Control-Allow-Credentials響應頭:

Access-Control-Allow-Credentials: true

若傳送請求時將withCredentials設為true,服務端沒有返回該響應頭,瀏覽器將拒絕該響應。

本來一切都是美好的,然而XDomainRequest不支援withCredentials屬性,於是IE8、IE9並不支援請求時包含識別資訊。

  • 預檢請求

使用CORS時,若請求方法不是GET,POST或HEAD,又或者使用了自定義HTTP頭,瀏覽器將發起預檢請求。

預檢請求是一個服務端認證機制,在執行請求之前會判斷該請求是否合法。

傳送複雜請求時,瀏覽器會將原始請求的方法和請求頭作為預檢請求的資訊傳送給伺服器;伺服器需要決定是否接受該請求並響應,預檢請求通常是使用一種OPTIONS的HTTP方法。

客戶端發起複雜請求時,會先發起預檢,攜帶以下頭資訊:

請求頭 描述
Origin 請求源
Access-Control-Request-Method 請求方法(HTTP方法)
Access-Control-Request-Headers 請求自定義頭(以逗號分隔)

伺服器返回的響應頭:

響應頭 描述
Access-Control-Allow-Origin 允許的請求源(與請求頭Origin匹配)
Access-Control-Allow-Methods 允許的請求方法(以逗號分隔)
Access-Control-Allow-Headers 允許的頭資訊(以逗號分隔)
Access-Control-Max-Age 預檢請求的快取時間(單位:秒)
Access-Control-Allow-Credentials 指定請求是否支援認證資訊(可選)

客戶端接收到服務端的響應後,會使用之前宣告的HTTP方法和請求頭髮送真正的請求。

預檢請求會被瀏覽器快取,快取時間為Access-Control-Max-Age值,快取時間內,後續相同型別的請求不再發起重複的預檢請求。IE8、IE9均不支援預檢請求。

4.HTML5 postMessage

HTML5的window.postMessage為瀏覽器帶來了一個安全的、基於事件的訊息API。與之前的子域名代理通過iframe跨子域通訊不同,使用postMessage不再是直接訪問一個文件的屬性和方法,而是向文件傳送訊息然後等待響應,這要求形成一條雙向的通訊通道。
更多有關postMessage的內容在下一篇闡述,先來個例項看看效果

歡迎踩踩我的個人部落格

相關文章