深入淺出FE(三)跨域Cross-Origin

目錄

 

1. 跨域是什麼

2. 爲什麼有跨域

2.1 防止csrf攻擊

2.2 防止xss攻擊

3. 跨域解決方案?

3.1 jsonp

3.2 "跨域資源共享"(Cross-origin resource sharing)CROS

3.3 document.domain + iframe跨域

3.4 window.name + iframe

3.5 location.hash + iframe跨域

3.6 window.postmessage

3.7 websocket

3.8 nginx代理(來自前端常見跨域解決方案(全))

3.9 中間件代理(其他語言同理)

4、拓展

5、參考資料


1. 跨域是什麼

介紹跨域之前,先來了解同源策略。同源策略就是瀏覽器爲了保證用戶信息的安全,防止惡意的網站竊取數據,禁止不同域之間的JS進行交互。對於瀏覽器而言只要域名、協議和端口其中一個不同就會引發同源策略,從而限制他們之間如下的交互行爲。

瀏覽器的同源策略會導致跨域,即兩個頁面只要域名、協議和端口三者任何一個不一致,請求都會跨域。

2. 爲什麼有跨域

跨域是爲了防止csrf和xss攻擊。

2.1 防止csrf攻擊

(1)當用戶訪問銀行www.bank.com,登陸並操作,這時用戶憑證如cookie等都生成並存放在瀏覽器;

(2)此時用戶又訪問了另一個釣魚網站,這時該釣魚網站網站就可以在它的頁面中,拿到銀行的cookie,比如用戶名,登陸token等,然後發起對www.bank.com的操作;

(3)如果這時瀏覽器如果沒有同源策略,銀行也沒有做響應的安全處理,那麼用戶就會被攻擊。

2.2 防止xss攻擊

xss攻擊包括三種類型-存儲型、反射型和DOM型,這裏主要是DOM型攻擊。

(1)有攻擊者製作了一個釣魚網站,這個網站內部嵌了一個和www.bank.com外觀一致的iframe;

(2)此時用戶進來後忽視了url不是銀行的的頁面,直接輸入賬戶名和密碼;

(3)如果這時瀏覽器如果沒有同源策略,那麼這些敏感信息就會被黑客獲取,銀行也沒有做相應的處理,那麼用戶就會被攻擊。

3. 跨域解決方案?

3.1 jsonp

3.1.1 原理

jsonp 是一種數據調用的方式。利用<script>標籤沒有跨域限制的“漏洞”。

3.1.2 用法

主要有兩種方式,一種是寫在url中,或者是動態生成script標籤,並插入到dom中,

當需要和後端通訊時,本站腳本創建一個<script>元素,地址指向第三方的API網址,形如:

寫在url中

<script src="http://querydata.com/jsonp/api/defalt?callback=querydata;"></script>

動態生成script,插入到dom中

// 創建一個腳本,並且告訴後端回調函數名叫querydata
var body = document.getElementsByTagName('body')[0];
var script = document.gerElement('script');
script.type = 'text/javasctipt';
script.src = 'demo.js?callback=querydata';
body.appendChild(script);


在本地提供一個回調函數querydata來接收數據(函數名可約定,或通過地址參數傳遞)。

function querydata(data){
    console.log(data)
}

產生的響應爲json數據的包裝(故稱之爲jsonp,即json padding),形如:

callback({"name":"coolsummer","gender":"Male"})

這樣瀏覽器會調用querydata函數,並傳遞解析後json對象作爲參數。本站腳本可在callback函數裏處理所傳入的數據。

3.1.3 優缺點及使用場景
3.1.3.1 jsonp 只能使用get請求,不能用於其他類型的請求。

3.1.3.2 沒有關於 JSONP 調用的錯誤處理。

如果動態腳本插入有效,就執行調用;如果無效,就靜默失敗。失敗是沒有任何提示的。例如,不能從服務器捕捉到 404 錯誤,也不能取消或重新開始請求。不過,等待一段時間還沒有響應的話,就不用理它了。(未來的 jQuery 版本可能有終止 JSONP 請求的特性)。

3.1.3.3 JSONP 被不信任的服務使用時會很危險。

因爲 JSONP 服務返回打包在函數調用中的 JSON 響應,而函數調用是由瀏覽器執行的,這使宿主 Web 應用程序更容易受到各類攻擊。如果打算使用 JSONP 服務,瞭解它能造成的威脅非常重要。

3.1.3.4 拓展(來自:jsonp的工作原理):

  • html標籤的src屬性沒有同源限制(支持跨域),瀏覽器解析script標籤時,會自動下載src屬性值(url)指向的資源;
  • script標籤指向的資源文件被下載後,其中的內容會被立即執行
  • 服務器端的程序會解析src屬性值中的url傳遞的參數,根據這些參數針對性返回一個/多個函數調用表達式,這些函數調用表達式的參數就是客戶端跨域想得到的數據
  • 服務器生成、返回的文件中,表達式調用的函數是已經在本地提前定義好的,而參數就是希望從跨域服務器拿到的數據。
  • 字面的script標籤可以,動態添加到dom樹中的script也可以,後者更方便綁定事件。

所以只要擁有”src”這個屬性的標籤都擁有跨域的能力,類似的可以跨域內嵌資源的還有:

(1)<script src=""></script>標籤嵌入跨域腳本。語法錯誤信息只能在同源腳本中捕捉到。上面jsonp也用到了呢。

(2) <link src="">標籤嵌入CSS。由於CSS的鬆散的語法規則,CSS的跨域需要一個設置正確的Content-Type消息頭。不同瀏覽器有不同的限制: IE, Firefox, Chrome, Safari (跳至CVE-2010-0051)部分 和 Opera。

(3)<video> 和 <audio>嵌入多媒體資源。

(4)<object>, <embed> 和 <applet>的插件。

(5)@font-face引入的字體。一些瀏覽器允許跨域字體( cross-origin fonts),一些需要同源字體(same-origin fonts)。

(6) <frame> 和 <iframe>載入的任何資源。站點可以使用X-Frame-Options消息頭來阻止這種形式的跨域交互。

(7) image

圖像ping是與服務器進行簡單、單向的跨域通信的一種方式,請求的數據是通過查詢字符串的形式發送的,而相應可以是任意內容,但通常是像素圖或204相應(No Content)。 圖像ping有兩個主要缺點:首先就是隻能發送get請求,其次就是無法訪問服務器的響應文本。

var img = new Image();
img.onload = img.onerror = function(){
    alert("done!");
};
img.src = "跨域資源路徑";
document.body.insertBefore(img,document.body.firstChild);

3.2 "跨域資源共享"(Cross-origin resource sharing)CROS

3.2.1 使用場景及優缺點

目前,所有瀏覽器都支持該功能(IE8+:IE8/9需要使用XDomainRequest對象來支持CORS),CORS也已經成爲主流的跨域解決方案。

整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與。對於開發者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺。

CORS與JSONP的使用目的相同,但是比JSONP更強大。JSONP只支持GET請求,CORS支持所有類型的HTTP請求。JSONP的優勢在於支持老式瀏覽器,以及可以向不支持CORS的網站請求數據。

3.2.2 原理

CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。

MDN解釋:

跨域資源共享(CORS)是一種機制,該機制使用附加的HTTP標頭來告訴瀏覽器以使Web應用程序在一個來源運行,並從另一個來源訪問選定的資源。Web應用程序請求其來源(域,協議或端口)不同的資源時,將執行跨域HTTP請求

3.2.3 用法

實現CORS通信的關鍵是服務器。只要服務器實現了CORS接口,就可以跨源通信。普通跨域請求只服務端設置Access-Control-Allow-Origin即可,前端無須設置.

若要帶cookie請求:前後端都需要設置。由於同源策略的限制,所讀取的cookie爲跨域請求接口所在域的cookie,而非當前頁。

瀏覽器請求分爲兩種:簡單請求和非簡單請求。

3.2.3.1 簡單請求

對於簡單請求,瀏覽器直接發出CORS請求。

具體來說,就是在頭信息之中,增加一個Origin字段。Origin字段用來說明本次請求來自哪個源(協議 + 域名 + 端口)。服務器根據這個值,決定是否同意這次請求。並且origin中的值必須在後端設置的Access-Control-Allow-Origin的值中,如果後端設置了Access-Control-Allow-Origin這個屬性爲‘*’,即origin可以爲任意值。

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

1)當origin不是服務端設置的Access-Control-Allow-Origin屬性中的值,服務器會返回一個正常的http響應,這時瀏覽器發現返回的response中沒有包含Access-Control-Allow-Origin字段,那麼會拋出錯誤,這個錯誤可以被xhr的onerror回調函數捕獲,但是這個錯誤無法通過http狀態碼識別,因爲可能狀態碼爲200.

2)當origin是服務端設置的Access-Control-Allow-Origin屬性的值(或者Access-Control-Allow-Origin屬性設置爲*),服務器會多返回幾個和跨域相關的字段:

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
  • Access-Control-Allow-Origin 服務端設置的Access-Control-Allow-Origin屬性的值(或者Access-Control-Allow-Origin屬性設置爲*,即允許所有的請求,這樣通常會導致更多的麻煩,比如對接口的攻擊等,一般設置爲指定的域);
  • Access-Control-Allow-Credentials 可選布爾值,服務端是否發送cookie,默認情況下,Cookie不包括在CORS請求之中,即此值爲false;此時如果想要在請求中帶上cookie,那麼前端需要設置帶上cookie的字段withCredentials字段。

以xhr、ajax或者axios請求爲例:

xhr

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

ajax

$.ajax({
        url: "http://localhost:9000",
        type: 'GET',
        xhrFields: {
            withCredentials: true // 這裏設置了withCredentials
        },
        success: function(data) {
            console.log(data)
        },
        error: function(err) {
            console.error(err)
        }
    })

axios

axios.defaults.withCredentials=true

服務端也要設置這個屬性爲true,不同語言設置方式各有不同,在此不一一舉例

Access-Control-Allow-Credentials: true

注意:前端和服務端必須同時設置,否則兩端任意一端設置,另一端不設置則不會攜帶cokie。但是省略withCredentials設置,有的瀏覽器還是會一起發送Cookie。這時,可以顯式關閉withCredentials。

xhr.withCredentials = false;
  • Access-Control-Expose-Headers 字段可選。

CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必須在Access-Control-Expose-Headers裏面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。

注意:如果要發送Cookie,Access-Control-Allow-Origin就不能設爲‘*’,必須指定明確的、與請求網頁一致的域名。同時,Cookie依然遵循同源政策,只有用服務器域名設置的Cookie纔會上傳,其他域名的Cookie並不會上傳,且(跨源)原網頁代碼中的document.cookie也無法讀取服務器域名下的Cookie。

簡單請求是滿足以下所有條件的請求(以下內容來自MDN:跨域資源共享(CORS),稍有修改):

注意:這些與Web內容已經可以發出的跨站點請求種類相同,除非服務器發送適當的標頭,否則不會向請求者釋放響應數據。因此,可以防止跨站點請求僞造的站點不必擔心HTTP訪問控制。

注: WebKit每日和Safari瀏覽器技術預覽放置在允許的值的額外限制AcceptAccept-LanguageContent-Language頭。如果這些標頭中的任何一個具有“非標準”值,則WebKit / Safari不會將請求視爲“簡單請求”。沒有記錄WebKit / Safari認爲“非標準”的值,以下WebKit錯誤除外:

沒有其他瀏覽器實現這些額外的限制,因爲它們不是規範的一部分。

3.2.3.2 非簡單請求

非簡單請求會在簡單請求發送之前,增加一次“預檢”請求。

“簡單請求”即不需要“預檢”,“預檢”請求首先通過OPTIONS方法將HTTP請求發送到另一個域上的資源,以確定實際請求是否可以安全發送。跨站點請求這樣被預檢,因爲它們可能會影響用戶數據。

瀏覽器在發送請求前只要不是簡單請求,就會先發一個預檢請求檢測請求是否被服務器接受,比如域名是否在服務器所允許的白名單中,以及可以使用哪些HTTP動詞和頭信息字段。

一個常見的預檢請求頭如下所示:

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

方法必須是OPTIONS

除了Origin字段,"預檢"請求的頭信息包括兩個特殊字段。

  • Access-Control-Request-Method告知服務器實際請求所使用的 HTTP 方法。
  • Access-Control-Request-Headers告知服務器實際請求所攜帶的自定義首部字段。該字段是一個逗號分隔的字符串,指定瀏覽器CORS請求會額外發送的頭信息字段

服務器基於從預檢請求獲得的信息來判斷,是否接受接下來的實際請求。

服務器收到"預檢"請求以後,檢查了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers字段以後,確認允許跨源請求,就可以做出迴應---是否允許跨域訪問。

//來自MDN
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2019 01:15:39 GMT 
Server: Apache/2.0.61 (Unix) 
Access-Control-Allow-Origin: http://foo.example 
Access-Control-Allow-Methods: POST, GET, OPTIONS 
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type 
Access-Control-Max-Age: 86400 
Vary: Accept-Encoding, Origin 
Content-Encoding: gzip 
Content-Length: 0 
Keep-Alive: timeout=2, max=100 
Connection: Keep-Alive 
Content-Type: text/plain

上面一段代碼是常見的預檢請求返回內容,最關鍵是Access-Control-Allow-Origin這個字段,表示http://api.bob.com可以請求數據。該字段也可以設爲星號,表示同意任意跨源請求。

如果瀏覽器否定了"預檢"請求,會返回一個正常的HTTP迴應,但是沒有任何CORS相關的頭信息字段。這時,瀏覽器就會認定,服務器不同意預檢請求,因此觸發一個錯誤,XMLHttpRequest對象的onerror回調函數捕獲。控制檯會打印出如下的報錯信息。

XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.

服務器迴應的其他CORS相關字段如下:

Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
  • Access-Control-Allow-Methods它的值是逗號分隔的一個字符串,表明服務器支持的所有跨域請求的方法。注意,和請求的Access-Control-Allow-Method字段區別,請求的Access-Control-Allow-Method字段是單個的,表示檔次請求的方法,是單個方法,而服務端響應的Access-Control-Allow-Methods返回的是所有支持的方法,而不單是瀏覽器請求的那個方法。這是爲了避免多次"預檢"請求。
  • Access-Control-Allow-Headers如果瀏覽器請求包括Access-Control-Request-Headers字段,則Access-Control-Allow-Headers字段是必需的。它也是一個逗號分隔的字符串,表明服務器支持的所有頭信息字段,不限於瀏覽器在"預檢"中請求的字段。
  • Access-Control-Allow-Credentials該字段與簡單請求時的含義相同,是否攜帶cookie。
  • Access-Control-Max-Age該字段可選,用來指定本次預檢請求的有效期,單位爲秒。上面結果中,有效期是20天(1728000秒),即允許緩存該條迴應1728000秒(即20天),在此期間,不用發出另一條預檢請求。

服務端通過預檢請求後:

一旦服務器通過了"預檢"請求,以後每次瀏覽器正常的CORS請求,就都跟簡單請求一樣,會有一個Origin頭信息字段。服務器的迴應,也都會有一個Access-Control-Allow-Origin頭信息字段。

下面是"預檢"請求之後,瀏覽器的正常CORS請求。

PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

上面頭信息的Origin字段是瀏覽器自動添加的。

下面是服務器正常的迴應。

Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8

上面頭信息中,Access-Control-Allow-Origin字段是每次迴應都必定包含的。

3.3 document.domain + iframe跨域

3.3.1 使用場景及優缺點

對於主域名相同,而子域名不同的情況,可以使用document.domain來跨域 這種方式非常適用於iframe跨域的情況。

缺點是隻能用於iframe,而且如果兩個頁面通信必須通過第三個頁面,並且傳遞值只能是單向。

MDN解釋:

Document 接口的 domain 屬性獲取/設置當前文檔的原始域部分,常用於同源策略

3.3.2 原理

兩個頁面都通過js強制設置document.domain爲基礎主域,就實現了同域。

3.3.3 用法(來自前端常見跨域解決方案(全)

1.)父窗口:(www.a.com/a.html)

<iframe id="iframe" src="http://child.a.com/b.html"></iframe>
<script>
    document.domain = 'a.com';
    var user = 'admin';
</script>

2.)子窗口:(child.a.com/b.html)

<script>
    document.domain = 'a.com';
    // 獲取父窗口中變量
    alert('get js data from parent ---> ' + window.parent.user);
</script>

3.4 window.name + iframe

3.4.1 使用場景及優缺點

瀏覽器窗口有window.name屬性。這個屬性的最大特點是,無論是否同源,只要在同一個窗口裏,前一個網頁設置了這個屬性,後一個網頁可以讀取它。並且可以支持非常長的 name 值(2MB)。

這種方法的優點是,window.name容量很大,可以放置非常長的字符串;缺點是必須監聽子窗口window.name屬性的變化,影響網頁性能。

3.4.2 原理

通過iframe的src屬性由外域轉向本地域,跨域數據即由iframe的window.name從外域傳遞到本地域。這個就巧妙地繞過了瀏覽器的跨域訪問限制,但同時它又是安全操作。

3.4.3 用法

父窗口先打開一個子窗口,載入一個不同源的網頁,該網頁將信息寫入window.name屬性。

window.name = data;

接着,子窗口跳回一個與主窗口同域的網址。

location = 'http://parent.url.com/xxx.html';

然後,主窗口就可以讀取子窗口的window.name了。

var data = document.getElementById('myFrame').contentWindow.name;

3.5 location.hash + iframe跨域

3.5.1 實現原理及優缺點

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

3.5.2 用法

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

嵌套關係爲: a.html中嵌套iframe(b.html),b.html中嵌套iframe(c.html),c中可以獲得a.html的對象。

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>

3.6 window.postmessage

3.6.1 使用場景和優缺點

a) 頁面和其打開的新窗口的數據傳遞

b) 多窗口之間消息傳遞

c) 頁面與嵌套的iframe消息傳遞

d) 上面三個場景的跨域數據傳遞

優點是公共的標準,使用簡單,缺點是用於接收消息的頁面可能存在跨站安全問題,因爲無法檢查origin和source屬性會導致跨站點腳本攻擊。

這個應該就是以後解決dom跨域通用方法了

3.6.2 原理

API爲window對象新增了一個window.postMessage方法,允許跨窗口通信,不論這兩個窗口是否同源。

3.6.3 用法(來自MDN)

/*
 * A窗口的域名是<http://example.com:8080>,以下是A窗口的script標籤下的代碼:
 */

var popup = window.open(...popup details...);

// 如果彈出框沒有被阻止且加載完成

// 這行語句沒有發送信息出去,即使假設當前頁面沒有改變location(因爲targetOrigin設置不對)
popup.postMessage("The user is 'bob' and the password is 'secret'",
                  "https://secure.example.net");

// 假設當前頁面沒有改變location,這條語句會成功添加message到發送隊列中去(targetOrigin設置對了)
popup.postMessage("hello there!", "http://example.org");

function receiveMessage(event)
{
  // 我們能相信信息的發送者嗎?  (也許這個發送者和我們最初打開的不是同一個頁面).
  if (event.origin !== "http://example.org")
    return;

  // event.source 是我們通過window.open打開的彈出頁面 popup
  // event.data 是 popup發送給當前頁面的消息 "hi there yourself!  the secret response is: rheeeeet!"
}
window.addEventListener("message", receiveMessage, false);
/*
 * 彈出頁 popup 域名是<http://example.org>,以下是script標籤中的代碼:
 */

//當A頁面postMessage被調用後,這個function被addEventListenner調用
function receiveMessage(event)
{
  // 我們能信任信息來源嗎?
  if (event.origin !== "http://example.com:8080")
    return;

  // event.source 就當前彈出頁的來源頁面
  // event.data 是 "hello there!"

  // 假設你已經驗證了所受到信息的origin (任何時候你都應該這樣做), 一個很方便的方式就是把event.source
  // 作爲回信的對象,並且把event.origin作爲targetOrigin
  event.source.postMessage("hi there yourself!  the secret response " +
                           "is: rheeeeet!",
                           event.origin);
}

window.addEventListener("message", receiveMessage, false);

注意:任何窗口可以在任何其他窗口訪問此方法,在任何時間,無論文檔在窗口中的位置,向其發送消息。 因此,用於接收消息的任何事件監聽器必須首先使用origin和source屬性來檢查消息的發送者的身份。 這不能低估:無法檢查origin和source屬性會導致跨站點腳本攻擊。

3.7 websocket

3.7.1 應用場景及優缺點

我們知道websocket是H5爲了解決http協議單項請求的缺陷。雖然可以用輪訓解決-每隔一段時間查詢狀態變化,但是這種方式存在延時且對服務端造成很大負載。

WebSocket 協議在2008年誕生,2011年成爲國際標準。所有瀏覽器都已經支持了。

websocket最大的特點就是服務器可以主動向客戶端推送信息,客戶端也可以主動向服務器發送信息,是真正的雙向平等對話,屬於服務器推送技術的一種。

websocket的特點:

(1)建立在 TCP 協議之上,服務器端的實現比較容易。

(2)與 HTTP 協議有着良好的兼容性。默認端口也是80和443,並且握手階段採用 HTTP 協議,因此握手時不容易屏蔽,能通過各種 HTTP 代理服務器。

(3)數據格式比較輕量,性能開銷小,通信高效。

(4)可以發送文本,也可以發送二進制數據。

(5)沒有同源限制,客戶端可以與任意服務器通信。

(6)協議標識符是ws(如果加密,則爲wss),服務器網址就是 URL。

3.7.2 原理

WebSocket是一種通信協議,使用ws://(非加密)和wss://(加密)作爲協議前綴。該協議不實行同源政策,只要服務器支持,就可以通過它進行跨源通信。

3.7.3 用法

如下是websocket的一個簡單的用法

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) { 
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log("Connection closed.");
};      

原生WebSocket API使用起來不太方便,可以使用Socket.io,它很好地封裝了webSocket接口,提供了更簡單、靈活的接口,也對不支持webSocket的瀏覽器提供了向下兼容。本此因爲是模擬就沒有安裝了用了WebSocket。

3.8 nginx代理(來自前端常見跨域解決方案(全))

1、 nginx配置解決iconfont跨域

瀏覽器跨域訪問js、css、img等常規靜態資源被同源策略許可,但iconfont字體文件(eot|otf|ttf|woff|svg)例外,此時可在nginx的靜態資源服務器中加入以下配置。

location / {
  add_header Access-Control-Allow-Origin *;
}

2、 nginx反向代理接口跨域

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

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

nginx具體配置:

#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;
    }
}

1.) 前端代碼示例:

var xhr = new XMLHttpRequest();

// 前端開關:瀏覽器是否讀寫cookie
xhr.withCredentials = true;

// 訪問nginx中的代理服務器
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);
xhr.send();

2.) Nodejs後臺示例:

var http = require('http');
var server = http.createServer();
var qs = require('querystring');

server.on('request', function(req, res) {
    var params = qs.parse(req.url.substring(2));

    // 向前臺寫cookie
    res.writeHead(200, {
        'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'   // HttpOnly:腳本無法讀取
    });

    res.write(JSON.stringify(params));
    res.end();
});

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

3.9 中間件代理(其他語言同理)

node中間件實現跨域代理,原理大致與nginx相同,都是通過啓一個代理服務器,實現數據的轉發,也可以通過設置cookieDomainRewrite參數修改響應頭中cookie中域名,實現當前域的cookie寫入,方便接口登錄認證。

4、拓展

1.注意本文主要討論前端的CROS,關於服務端的CROS可參考:服務器端訪問控制(CORS)

2.通過離線緩存,如localhost和indexdb也能夠跨域

3.阮一峯文章瀏覽器同源政策及其規避方法中提到的片段識別碼是一種不太常見的方式。

5、參考資料

1.跨域資源共享 CORS 詳解

2.jsonp的工作原理

3.結合 JSONP 和 jQuery 快速構建強大的 mashup

4.跨域資源共享 CORS 詳解

5.跨域資源共享(CORS)

6.Document.domain

7.前端常見跨域解決方案(全)

8.SendMessage、PostMessage原理

9.WebSocket 教程

10.瀏覽器同源政策及其規避方法

發佈了380 篇原創文章 · 獲贊 115 · 訪問量 39萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章