一文梳理同源策略與跨域技術

1.同源策略

同源策略是一個重要的安全策略,它用於限制一個origin的文檔或者它加載的腳本如何能與另一個源的資源進行交互。它能幫助阻隔惡意文檔,減少可能被攻擊的媒介。

1.1何謂同源

如果兩個 URL 的 protocolport (如果有指定的話)和 host 都相同的話,則這兩個 URL 是同源。這個方案也被稱爲“協議/主機/端口元組”,或者直接是 “元組”。

下表給出了與 URL http://store.company.com/dir/page.html 的源進行對比的示例:

URL 結果 原因
http://store.company.com/dir2/other.html 同源 只有路徑不同
http://store.company.com/dir/inner/another.html 同源 只有路徑不同
https://store.company.com/secure.html 失敗 協議不同
http://store.company.com:81/dir/etc.html 失敗 端口不同 ( http:// 默認端口是80)
http://news.company.com/dir/other.html 失敗 主機不同

隨着互聯網的發展,"同源政策"越來越嚴格。目前,如果非同源,共有三種行爲受到限制。

(1) Cookie、LocalStorage 和 IndexDB 無法讀取。

(2) DOM 無法獲得。

(3) AJAX 請求不能發送。

雖然這些限制是必要的,但是有時很不方便,合理的用途也受到影響。

2.跨域

2.1 何謂跨域?

跨域問題是由於瀏覽器爲了防止CSRF(跨站請求僞造)攻擊,避免惡意攻擊而帶來的風險而採取的同源策略限制。當一個頁面中使用XMLHTTPRequest對象發送HTTP請求時(XHR請求),必須保證當前頁面和請求的對象是同源的

能實現跨域的技術還是比較多的,篇幅有限,這篇文章主要給大家帶來:

  • JSONP跨域
  • CORS跨域
  • postmessage
  • 服務器代理
  • WebSocket

2.2 JSONP跨域

JSONP的原理:靜態資源請求不受同源策略影響。具體:瀏覽器只對XHR(XMLHttpRequest)請求有同源請求限制,而對script標籤src屬性、link標籤ref屬性和img標籤src屬性沒有這種限制,利用這個“漏洞”就可以很好的解決跨域請求.

JSONP 由兩部分組成:回調函數和數據。回調函數是當響應到來時應該在頁面中調用的函數。回調
函數的名字一般是在請求中指定的。而數據就是傳入回調函數中的JSON數據。

<script>
    function jsoncallback(response){
        console.log(response);
    }
    var script = document.createElement("script");
    script.src = "https://me.kervi.cn/v1/datas/jsondata.php?callback=jsoncallback";
    document.body.insertBefore(script, document.body.firstChild);
</script>

JSONP 是通過動態<script> 元素來使用的,使用時可以爲src 屬性指定一個跨域 URL。

總結:

  • 簡單易用
  • 能夠直接訪問響應文本,支持在瀏覽器與服務器之間雙向通信
  • 僅支持GET請求(通過script標籤的的src屬性發送);
  • 需要後端配合,前後端約定一個字段名,這裏約定的jsoncallback,來傳遞一個函數名,從而使得前端可以使用對應的callback函數,拿到數據,處理數據。
  • JSONP 是從其他域中加載代碼執行。如果其他域不安全,很可能會在響應中夾帶一些惡意代碼,而此時除了完全放棄 JSONP 調用之外,沒有辦法追究。因此在使用不是你自己運維的 Web 服務時,一定得保證它安全可靠;
  • 要確定 JSONP 請求是否失敗並不容易。雖然 HTML5 給 <script> 元素新增了一個 onerror事件處理程序,但目前還沒有得到任何瀏覽器支持。爲此,開發人員不得不使用計時器檢測指定時間內是否接收到了響應。但就算這樣也不能盡如人意,畢竟不是每個用戶上網的速度和帶寬都一樣。

2.3 CORS跨域

CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。它允許瀏覽器向跨源服務器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。它使用額外的 HTTP 頭來告訴瀏覽器 讓運行在一個 origin (domain) 上的Web應用被准許訪問來自不同源服務器上的指定的資源。

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

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

因此,實現CORS通信的關鍵是服務器。只要服務器實現了CORS接口,就可以跨源通信。

在各種服務端代碼實現如下:

// 根據不同語言規則,具體語法有所不同,此處以NodeJs的express爲例
//設置跨域訪問  
app.all('*', function(req, res, next) {  
    res.header("Access-Control-Allow-Origin", "*");  
    res.header("Access-Control-Allow-Headers", "X-Requested-With");  
    res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
    next();  
});   

Nginx實現如下:

server {
    ... 
    add_header Access-Control-Allow-Credentials true;
    add_header Access-Control-Allow-Origin $http_origin;
        
    location /file {
        if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin $http_origin;
            add_header Access-Control-Allow-Methods $http_access_control_request_method;
            add_header Access-Control-Allow-Credentials true;
            add_header Access-Control-Allow-Headers $http_access_control_request_headers;
            add_header Access-Control-Max-Age 1728000;
            return 204;
        }   
    }
    ...
}

2.4 postMessage

「window.postMessage()」 方法可以安全地實現跨源通信。可以允許來自不同源的腳本採用異步方式進行有限的通信,可以實現跨文本文檔,多窗口,跨域消息傳遞.多用於窗口間數據通信,這也使它成爲跨域通信的一種有效的解決方案.

兼容性:

語法

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

  • message 你要發送的信息(字符串和對象都可以)
  • targetOrigin 你要發送信息的目標域名
  • transfer 可選參數,與消息一起傳輸的Transferable對象序列。這些對象的所有權將提供給目標端,並且它們在發送端不再可用。

已經有人寫了不錯的實踐,博主這裏就不做介紹,可以閱讀postmessage可真太有用了瞭解。

總結

  • postMessage是html5新增的一個解決跨域的方法,主要解決不同源的腳本採用異步方式進行有限的通信,可以實現跨文本檔、多窗口、跨域消息傳遞,多用於頁面與嵌套的iframe消息傳遞

  • 由於默認允許跨域,所以導致一些安全問題,主要攻擊方式有兩種,一是僞造數據發送端,易造成XSS,二是僞造數據獲取端,類似JSONP劫持。

  • 用於接收消息的任何事件偵聽器必須首先使用origin和可能的source屬性檢查消息發送者的身份。未能檢查origin和可能的source屬性可以實現跨站點腳本攻擊。

2.5 websocket

WebSocket 是一種網絡通信協議,可在網絡瀏覽器和服務器之間建立“套接字”連接。這種方式本質沒有使用了 HTTP 的響應頭, 因此也沒有跨域的限制。

前端部分(代碼來自秋風的筆記-10種跨域解決方案(附終極大招)

<script>  let socket = new WebSocket("ws://localhost:8080");  socket.onopen = function() {    socket.send("秋風的筆記");  };  socket.onmessage = function(e) {    console.log(e.data);  };</script>複製代碼

後端部分

const WebSocket = require("ws");const server = new WebSocket.Server({ port: 8080 });server.on("connection", function(socket) {  socket.on("message", function(data) {    socket.send(data);  });});

總結

  • 建立在 TCP 協議之上,服務器端的實現比較容易。
  • 與 HTTP 協議有着良好的兼容性。默認端口也是80和443,並且握手階段採用 HTTP 協議,因此握手時不容易屏蔽,能通過各種 HTTP 代理服務器。
  • 數據格式比較輕量,性能開銷小,通信高效。
  • 可以發送文本,也可以發送二進制數據。
  • 沒有同源限制,客戶端可以與任意服務器通信。
  • 協議標識符是ws(如果加密,則爲wss),服務器網址就是 URL。

2.6 Nginx反向代理

這裏需要說明的是: 跨域是瀏覽器行爲,不是服務器行爲。同源策略僅是針對瀏覽器的安全策略。服務器端調用HTTP接口只是使用HTTP協議,不需要同源策略,也就不存在跨域問題。

實際上,請求已經到達服務器了,只不過在回來的時候被瀏覽器限制了

實現思路:通過Nginx配置一個代理服務器域名與發出請求域名相同,端口不同,反向代理訪問目標域名。

背景:domain1需要跨域訪問domain2

server {
        listen 80;
        server_name www.domain1.com;
        location / {
            proxy_pass www.domain2.com:8080;
        }
}

需要說明的是,前端跨域的技術還有許多,只是限於篇幅沒有講到,比如node正向代理,window.name+iframe等。還想了解別的跨域方案的小夥伴可以看本文的參考文章去觀摩觀摩。

小結

同源策略僅僅是針對瀏覽器的安全策略,跨域是瀏覽器行爲,如果兩個 URL 的 protocolport (如果有指定的話)和 host 都相同的話,則這兩個 URL 是同源。現在有許多的跨域方案可供大家選擇,本文僅僅選擇了JSONP、CORS、postMessage等6種跨域技術進行了闡述,希望能在梳理自己知識點的同時幫助到一些人。

參考文章:

jsonp完美解決跨域問題,簡單

跨域解決方案實踐cors及jsonp

跨資源共享cors詳解-阮一峯

瀏覽器同源政策及其規避方法-阮一峯

postmessage濫用導致的安全問題

10種跨域解決方案(附終極大招)

websocket教程-阮一峯

9種常見的前端跨域解決方案

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章