跨域方法整理

一、跨域的產生背景

1、什麼是同源策略?
同源,指的是兩個頁面的協議、端口和域名都相同,則兩個頁面具有相同的源。

同源策略限制了從同一個源加載的文檔或者腳本如何與來自另一個源的資源進行交互。

同源策略是瀏覽器的一個安全功能,不同源的客戶端腳本在沒有明確授權的情況下,不能讀寫對方資源。只有同一個源的腳本賦予dom、讀寫cookie、session和ajax等操作的權限。
2、爲什麼src屬性加載的資源可以實現跨域?
不受同源策略的限制,帶有`src`屬性的標籤加載時,實際上是由瀏覽器發起的一次GET請求,不同於XMLHTTPRequest,他們是通過src屬性加載的資源。瀏覽器限制了JavaScript的權限,使其【不能讀、寫】其中返回的內容。

對於XMLHTTPRequest,受同源策略的限制,它可以訪問同源對象的內容,但是不能訪問跨域資源。
W3C制定了XMLHTTPRequest跨域訪問標準,它需要目標域返回的http頭來授權是否允許跨域訪問,因爲http頭對JS來說是無法控制的。
3、跨源網絡訪問

同源策略限制了不同源之間的交互,這些交互通常分爲三類:

- 通常允許跨域寫操作
- 通常允許跨域資源嵌入
- 通常不允許跨域讀操作

可以嵌入跨源的資源

- 【<script src="..."></script>】標籤嵌入跨域腳本。語法錯誤信息只能在同源腳本中捕捉到。
- 【<link rel="stylesheet" href="..."/>】標籤嵌入CSS,CSS的跨域需要一個設置正確的Content-type消息頭。
- 【<img src="...">】嵌入圖片
- 【<video>和<audio>】嵌入多媒體資源。
- 【@font-face】引入的字體
- 【<frame>和<iframe>】載入的任何資源。站點可以使用X-Frame-Options消息頭來阻止這種形式的跨域交互。

二、常見的跨域方法

【JSONP跨域】
1、JSONP實現跨域的原理

  在JS中,對於<script><img><link></iframe>等標籤都可以跨域加載資源,這是因爲對於這些標籤的src或者href屬性加載資源時,不受同源策略的限制,瀏覽器不會限制內聯腳本的執行。

  使用JSONP實際上是由瀏覽器發起了一次get請求,通過在src屬性的url後邊添加一個回調函數。對於XMLHttpRequest發起的請求,瀏覽器限制了JS的權限,使其不能讀寫其返回的內容;XMLHttpRequest可以訪問同源對象的內容,但是由於同源策略的約束,不能訪問跨域資源。

  W3C制定了xhr跨域訪問的標準,它需要目標域返回的http頭來授權是否允許跨域訪問,http頭對於JS來說是無法控制的。

2、JSONP組成

  JSONP由兩部分組成:回調函數和數據。
  回調函數是當響應到來時應該在頁面中調用的函數; 數據就是傳入回調函數中的JSON數據

3、JSONP的優缺點

  優點:
    - 兼容性好,可用於解決主瀏覽器的跨域數據訪問的問題;
    - 能夠直接訪問響應文本,支持在瀏覽器與服務器之間雙向通信;

  缺點:
    - 存在安全隱患,JSONP是從其他域中加載代碼執行,如果其他域不安全,則不安全;可能會遭受XSS攻擊;
    - 確定JSONP請求是否失敗不容易

4、JSONP的實現流程

  - 聲明一個回調函數,其函數名當作參數值,要傳遞給跨域請求數據的服務器,函數行參爲要獲取的目標數據;
  - 創建一個

5、函數封裝
function jsonp ({url, params, callback}) {
    return new Promise (function (resolve, reject) {
        let script = document.createElement('script');
        window[callback] = function (data) {
            resolve(data);
            document.body.removeChild(script);
        }
        params = {...params, callback};
        let arr = [];
        for (let key in arr) {
            arr.push(`${key}=${params[key]}`);
        }
        script.src = `${url}?${arr.join('&')}`;
        document.body.appendChild(script);
    })
}
jsonp({
    url: '',
    params: {},
    callback: '函數名'
}).then(function (data) {
    console.log(data);
})
6、實現
function getJSONP(url,callback){
    //根據指定的URL發送一個JSONP請求
    //然後將解析得到的響應數據發送給回調函數
    var hdNum = "callback" + getJSONP.counter++;//每次自增計數器
    var hdName = "getJSONP." + hdNum;//創建回調函數的名字,將計數器作爲回調函數的屬性

    if(url.indexOf("?") === -1){
        url += ">jsonp=" + hdName;
    }else{
        url += "&jsonp=" + hdName;
    }

    //創建script元素用於發送請求
    var script = document.createElement("script");
    getJSONP[hdName] = function(response){
        try{
            callback(response);
        }finally{
            delete getJSONP[hdNum];
            script.parentNode.removeChild(script);
        }
    };
    script.src = url;
    document.body.appendChild(script);
}
getJSONP.counter = 0;
var s = document.getElementById('s');
var list = document.getElementById('list');
s.oninput = function(){
    var script = document.createElement('script');
    script.src = 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=' + this.value+ '&json=1&p=3&sid=1432_18280_17942_21080_18559_21454_21394_21378_21191&req=2&csor=1&cb=fn' ;
        document.body.appendChild(script);
    };
    function fn( data ){
        // console.log(data);
        var str = '' ;
        for (var i = 0; i < data.s.length; i++ ){
            str += '<li>'+ data.s[i] +'</li>' ;
        }
        list.innerHTML = str;
    }
}
【CORS跨域】
1、ajax通信的限制

  通過XHR實現Ajax通信的一個主要限制,來源於跨域安全策略。
  默認情況下,XHR對象只能訪問與包含它的頁面位於同一個域中的資源。

2、CORS跨域資源共享

  CORS是一個W3C標準,全稱是“跨域資源共享”。定義了在必須訪問跨源資源時,瀏覽器和服務器應該如何溝通。它允許瀏覽器向跨源服務器發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。

3、CORS實現跨域的基本思想

  使用自定義的HTTP頭部,讓瀏覽器與服務器進行溝通,從而決定請求和響應應該成功還是失敗。

【注意事項】
	並不一定是瀏覽器發起了跨站請求,也可能是請求能夠正常被髮送,但是返回結果被瀏覽器給攔截了。
	跨域資源共享機制允許web應用服務器進行跨域訪問控制,才保證數據傳輸得以安全運行。

例如:
    - 在發送簡單請求時,它沒有自定義的頭部,而主體內容是text/plain。在發送該請求時,需要給它附加一個額外的Origin頭部,其中包含請求頁面的源信息(協議、域名和端口),以便服務器根據這個頭部信息來決定是否給予響應。
    - 如果服務器認爲這個請求可以接收,就會在Access-Control-Allow-Origin頭部中返回相同的源信息;如果沒有這個頭部,或者有頭部但源信息不匹配,瀏覽器就會駁回請求。
    
    【注意:請求和響應都不包含cookie信息】
4、CORS通信

  CORS需要瀏覽器和服務器同時支持。目前,所有瀏覽器都支持該功能,IE不能低於10。

整個CORS通信過程都是瀏覽器自動完成,不需要用戶參與。
對於開發者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。
瀏覽器一旦發現AJAX請求跨域,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺。【這個請求是預檢請求】
實現CORS通信的關鍵是服務器,只要服務器實現了CORS接口,就可以實現跨源通信。
5、CORS請求的分類

  瀏覽器將CORS請求分爲兩類:簡單請求和非簡單請求。

5-1、簡單請求
簡單請求支持的方法有:HEAD、GET、POST
頭信息字段:
    Accept
    Accept-Language
    Content-Language
    Last-Event-ID
    Content-Type【只可以取如下值:application/x-www-form-urlencoded、multipart/form-data、text/plain】
 # 簡單請求的描述
對於簡單請求,瀏覽器直接發出CORS請求,簡單來說,就是在頭信息之中添加一個Origin字段。
GET /cors HTTP/1.1
Origin: http://api.com
Host:api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: ...
上述的頭信息中,Origin字段用來說明本次請求來自哪個源(協議+域名+端口號),服務器根據這個值,決定是否同意這次請求。

如果Origin指定的源,在不許可範圍內,服務器會返回一個正常的HTTP迴應。瀏覽器發現,這個迴應的頭信息沒有包含Access-Control-Allow-Origin字段,就知道錯了,從而拋出一個錯誤,被XMLHttpRequest的onerror回調函數捕獲。這種錯誤無法通過狀態碼識別,因爲HTTP迴應的狀態碼可能是200。
 # 響應字段
如果Origin指定的源,在許可範圍內,服務器返回的響應,會多出幾個頭信息字段:
Access-Control-Allow-Origin:
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: 
Content-Type: text/html; charset=utf-8
 # 服務器請求的字段
Access-Control-Allow-Origin
    該字段是必須的,他的值要麼是請求時Origin字段的值,要麼是一個*,表示接受任意域名的請求

Access-Control-Allow-Credentials
    - 該字段可選,他的值是一個布爾值,表示是否允許發送Cookie。默認情況下,Cookie不包含在CORS請求之中。設爲true,即表示服務器明確許可。cookie可以包含在請求中,一起發給服務器。
    - 這個值也只能設置爲true,如果服務器不要瀏覽器發送cookie,刪除該字段即可。
    - 將XMLHttpRequest的withCredentials標誌設置爲true,向服務器發送cookies。如果服務器端的響應中未攜帶Access-Control-Allow-Credentials:true,瀏覽器將不會把響應內容返回給請求的發送者

Access-Control-Expose-Headers
    - 該字段可選,CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、pragma。
    - 如果想要拿到其他字段,就必須在Access-Control-Expose-Headers裏面指定

withCredentials屬性
    - CORS請求默認不發送cookie和HTTP認證信息。如果要把cookie發送到服務器,一方面要服務器同意,指定Access-Control-Allow-Credentials字段,另一方面,開發者必須在AJAX請求中打開withCredentials屬性【var xhr = new XMLHttpRequest();xhr.withCredentials=true;】;
    - 如果不設置,即使服務器同意發送cookie,瀏覽器也不會發送;或者,服務器要求設置cookie,瀏覽器也不會處理。


- 需要注意的是:
    如果要發送cookie,Access-Control-Allow-Origin就不能設置爲*,必須指定明確的、與請求網頁一致的域名。
    同時,cookie依然遵循同源策略,只有用服務器域名設置的cookie才能上傳,其他域名的cookie並不會上傳,且跨源-原網頁中的代碼的document.cookie也無法讀取服務器域名下的cookie
5-2、非簡單請求
非簡單請求是那種對服務器有特殊要求的請求,比如請求方法是PUT或DELETE,或者,Content-Type的值爲application/json。

非簡單請求的CORS請求,會在正式通信之前,增加一次HTTP查詢請求,稱爲預檢請求。

瀏覽器先詢問服務器,當前網頁所在的域名是否在服務器的許可名單中,以及可以使用哪些HTTP動詞和頭信息字段。只有得到肯定的答覆,瀏覽器纔會發出正式的XMLHttpRequest請求,否則就報錯。
5-3、預檢請求
需要預檢的請求必須首先使用options方法發起一個預檢請求到服務器,以獲知服務器是否允許該實際請求。
預檢請求的使用,可以避免跨域請求對服務器的用戶數據產生未預期的影響。
發送預檢請求的條件:
    1、發送方法如下:put delete connect options trace patch
    2、設置了以下字段:
        Accept Accept-Language
        Content-Language
        Content-Type[不是下列值:text/plain mutipart/form-data application/x-www-form-urlencoded]
    3、請求中的XMLHttpRequestUpload對象註冊了任意多個事件監聽器
    4、請求中使用了ReadableStream對象
var url = '';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', true);
xhr.send();
瀏覽器發現上述請求是一個非簡單請求,就會自動發出一個預檢請求,要求服務器確認可以這樣請求,
預檢請求用的方法是OPTIONS,表示這個請求是用來詢問的,頭信息裏面的關鍵字段是Origin,表示請求來自哪個源;除了Origin,預檢請求的頭信息必須包含以下兩個字段:
    Access-Control-Request-Method:該字段是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法
    Access-Control-Request-Headers: 該字段值是一個用逗號分隔的字符串,指定瀏覽器CORS請求會額外發送的頭信息字段
 # 預檢請求的迴應
服務器收到預檢請求以後,檢查了Origin、Access-Control-Request-Method、Access-Control-Request-Headers字段以後,確認允許跨源請求,就可以做出迴應。

如果瀏覽器否定了預檢請求,會返回一個正常的HTTP迴應,但是沒有任何CORS相關的頭信息字段。這時,瀏覽器就會認定,服務器不同意預檢請求,因此觸發一個錯誤,被XHR的onerror函數捕獲
 # 服務器迴應的相關字段
Access-Control-Allow-Methods
    該字段必須,他的值是一個以逗號分隔的字符串,表明服務器支持的所有跨域請求的方法。注意:返回的是所有支持的方法,而不單是瀏覽器請求的那個方法,這是爲了避免多次預檢請求

Access-Control-Allow-Headers
    該字段是必須的,它的值是一個以逗號分隔的字符串,表示服務器支持的所有頭信息字段,不限於瀏覽器在預檢請求中的字段

Access-Control-Allow-Credentials
    是否允許存儲cookie和發送HTTP憑證
    
Access-Control-Max-Age
    該字段可選,用來指定本次預檢請求的有效期,單位爲秒,在有效期內,不需要發出另一條預檢請求
6、跨域請求功能概述

  跨域資源共享標準新增了一組HTTP首部字段,允許服務器聲明哪些源站通過瀏覽器有有限訪問哪些資源。
  根據規範,對於那些可能對服務器數據產生副作用的HTTP請求方法【特別是GET以外的請求,或者搭配某些MIME類型的post請求】,瀏覽器必須首先使用OPTIONS方法發起一個預檢請求,從而獲取服務端是否允許該跨域請求。服務器確認允許後,纔會發起實際的HTTP請求。
  在預檢請求的返回中,服務器端也可以通知客戶端是否需要攜帶身份憑證【包含cookies和HTTP認證相關數據】
  CORS請求失敗會產生錯誤,但是爲了安全,在JS代碼層面是無法獲知到底是哪裏出了問題。只能通過查看瀏覽器的控制檯得知哪裏出現了錯誤。

7、附帶身份憑證的請求與通配符:

  對於附帶身份憑證的請求,服務器不得設置Access-Control-Allow-Origin的值爲*。因爲請求的頭部中攜帶了cookie信息,如果Access-Control-Allow-Origin的值設置爲*,請求將會失敗。如果將他的值設置爲一個具體的值,則請求將成功執行。

8、與JSONP跨域的區別:
  CORS跨域比JSONP更強大,JSONP跨域只支持get請求,CORS跨域支持所有類型的HTTP請求。JSONP的優勢雜居支持老式瀏覽器,以及向不支持CORS的網站請求數據。

【websocket跨域】
1、定義

  WebSocket是HTML5開始提供的一種在單個TCP連接上進行全雙工通信的協議。

2、作用

  WebSocket使得客戶端和服務器之間的數據交換變得更加簡單,允許服務器主動向客戶端發送數據。

  在WebSocket API中,瀏覽器和服務器之間只需要完成一次握手,兩者之間就可以創建持久性連接,並進行雙向數據傳輸。

  在WebSocket API中,瀏覽器和服務器只需要做一個握手的動作,瀏覽器和服務器之間就形成了一道快速通道。兩者之間就可以直接互相傳送數據。

  WebSocket協議能更好的節省服務器資源和帶寬,並且能夠更實時地進行通訊。

AJAX輪詢:
    在特定的時間間隔,由瀏覽器對服務器發出http請求,然後由服務器返回最新的數據給客戶端的瀏覽器。
    
缺點:
    瀏覽器需要不斷的向服務器發出請求,然而,http請求可能包含較長的頭部,真正有效的數據可能只有一小部分,會浪費很多帶寬。
3、WebSocket屬性
3-1、readyState
readyState,只讀屬性,表示連接狀態,取值如下:
- 0:表示連接尚未建立;
- 1:表示連接已經建立,可以進行通信;
- 2:表示連接正在進行關閉;
- 3:表示連接已經關閉或者連接不能打開;
3-2、bufferedAmount
只讀屬性bufferedAmount已被send方法放入,正在等待傳輸,但是還沒有發出UTF-8文本字節數。
4、WebSocket事件
- open:連接建立時觸發;
- message:客戶端接收服務端數據時觸發;
- error:通信發生錯誤時觸發;
- close:連接關閉時觸發;
5、WebSocket方法
- send:使用連接發送數據;
- close:關閉連接;
6、WebSocket實例

  WebSocket協議本質上是一個基於TCP的協議。

  爲了建立一個WebSocket連接,客戶端瀏覽器首先要向服務器發起一個HTTP請求,這個請求和通常的HTTP請求不同,包含了一些附加頭信息,其中,附加頭信息“Upgrade:WebSocket”表明這是一個申請協議升級的HTTP請求,服務器端解析這些附加的頭信息,然後產生應答信息返回給客戶端,客戶端和服務器端的WebSocket連接就建立起來了,雙方就可以通過這個連接通道自由的傳遞信息,並且這個連接會持續存在,直到客戶端或服務器端的某一方主動的關閉連接。

【代碼實現】
function WebSocketTest () {
    if ("WebSocket" in window) {
        var ws = new WebSocket("ws://localhost:1234/echo");
        ws.onopen = function () {
            ws.send('發送數據');
        }
        ws.onmessage = function (data) {
            console.log(data.data);
        }
        ws.onclose = function () {
            
        }
    }
}
【其他】
WebSocket使用和http相同的TCP端口,可以繞過大多數防火牆的限制。
默認情況下,WebSocket協議使用80端口,運行在TSL之上時,默認使用443端口。
Socket是傳輸控制層協議,WebSocket是應用層協議。
【postMessage跨域】
1、定義
window.postMessage()是HTML5 XMLHttpRequest level2中的API,且是爲數不多的可以跨域的window屬性之一;
postMessage()方法允許來自不同源的腳本採用異步方式進行有限的通信,可以實現跨文檔、多窗口、跨域消息傳遞;

  window.postMessage()方法可以安全的實現跨源通信。通常,對於兩個不同頁面的腳本,只有當執行他們的頁面位於具有相同的協議、端口、域名時,這兩個腳本才能相互通信。

  window.postMessage()方法被調用時,會在頁面所有腳本執行結束之後,向目標窗口派發一個MessageEvent消息。該MessageEvent消息有4個屬性需要注意:

- message屬性表示該message的類型;
- data屬性爲window.postMessage()的第一個參數;
- origin屬性表示window.postMessage()方法執行時,調用頁面的當前狀態;
- source屬性表示調用window.postMessage()方法的窗口信息;
2、解決的問題
- 頁面和其打開的新窗口的數據傳遞;
- 多窗口之前消息傳遞;
- 頁面與嵌套的iframe消息傳遞;
- 跨域數據傳遞;
3、語法

otherWindow.postMessage(message, targetOrigin, [transfer])

 【參數描述】:
- message:將要發送到其他window的數據;可以不受限制的將數據對象安全的傳送到目標窗口而無需序列化;
- targetOrigin:指定哪些窗口可以來接收到消息事件;其值可以是字符串或者URI;
    如果明確的知道消息應該發送到哪個窗口,那麼targetOrigin應該有一個確切的值,而不是*,不提供確切的目標將導致數據泄漏到任何對數據感興趣的惡意站點。
4、實現
// a.html
<iframe src="http://localhost:9000/b.html" id="iframe" "load()"></iframe>
<script type="text/javascript">
    function load () {
        var iframe = document.getElementById('iframe');
        console.log(iframe);
        window.postMessage('123', 'http://localhost:9000');
        window.onmessage = function (e) {
            console.log(e.data);
    }
}
</script>

// b.html
<script type="text/javascript">
    window.onmessage = function (e) {
        console.log(e.data);
        e.source.postMessage('456', e.origin);
    }
</script>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章