瀏覽器同源策略及跨域的解決方法

来源:https://www.cnblogs.com/laixiangran/archive/2018/05/20/9064769.html
-Advertisement-
Play Games

什麼是瀏覽器同源策略 同源策略(Same origin policy)是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響。可以說 Web 是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現。 它的核心就在於它認為自任何站點裝載的信賴內容是 ...


什麼是瀏覽器同源策略

同源策略(Same origin policy)是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響。可以說 Web 是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現。

它的核心就在於它認為自任何站點裝載的信賴內容是不安全的。當被瀏覽器半信半疑的腳本運行在沙箱時,它們應該只被允許訪問來自同一站點的資源,而不是那些來自其它站點可能懷有惡意的資源。

所謂同源是指:功能變數名稱、協議、埠相同。

下表是相對於 http://www.laixiangran.cn/home/index.html 的同源檢測結果:

另外,同源策略又分為以下兩種:

  1. DOM 同源策略:禁止對不同源頁面 DOM 進行操作。這裡主要場景是 iframe 跨域的情況,不同功能變數名稱的 iframe 是限制互相訪問的。
  2. XMLHttpRequest 同源策略:禁止使用 XHR 對象向不同源的伺服器地址發起 HTTP 請求。

為什麼要有跨域限制

因為存在瀏覽器同源策略,所以才會有跨域問題。那麼瀏覽器是出於何種原因會有跨域的限制呢。其實不難想到,跨域限制主要的目的就是為了用戶的上網安全。

如果瀏覽器沒有同源策略,會存在什麼樣的安全問題呢。下麵從 DOM 同源策略和 XMLHttpRequest 同源策略來舉例說明:

如果沒有 DOM 同源策略,也就是說不同域的 iframe 之間可以相互訪問,那麼黑客可以這樣進行攻擊:

  1. 做一個假網站,裡面用 iframe 嵌套一個銀行網站 http://mybank.com
  2. 把 iframe 寬高啥的調整到頁面全部,這樣用戶進來除了功能變數名稱,別的部分和銀行的網站沒有任何差別。
  3. 這時如果用戶輸入賬號密碼,我們的主網站可以跨域訪問到 http://mybank.com 的 dom 節點,就可以拿到用戶的賬戶密碼了。

如果 XMLHttpRequest 同源策略,那麼黑客可以進行 CSRF(跨站請求偽造) 攻擊:

  1. 用戶登錄了自己的銀行頁面 http://mybank.comhttp://mybank.com 向用戶的 cookie 中添加用戶標識。
  2. 用戶瀏覽了惡意頁面 http://evil.com,執行了頁面中的惡意 AJAX 請求代碼。
  3. http://evil.comhttp://mybank.com 發起 AJAX HTTP 請求,請求會預設把 http://mybank.com 對應 cookie 也同時發送過去。
  4. 銀行頁面從發送的 cookie 中提取用戶標識,驗證用戶無誤,response 中返回請求數據。此時數據就泄露了。
  5. 而且由於 Ajax 在後臺執行,用戶無法感知這一過程。

因此,有了瀏覽器同源策略,我們才能更安全的上網。

跨域的解決方法

從上面我們瞭解到了瀏覽器同源策略的作用,也正是有了跨域限制,才使我們能安全的上網。但是在實際中,有時候我們需要突破這樣的限制,因此下麵將介紹幾種跨域的解決方法。

CORS(跨域資源共用)

CORS(Cross-origin resource sharing,跨域資源共用)是一個 W3C 標準,定義了在必須訪問跨域資源時,瀏覽器與伺服器應該如何溝通。CORS 背後的基本思想,就是使用自定義的 HTTP 頭部讓瀏覽器與伺服器進行溝通,從而決定請求或響應是應該成功,還是應該失敗。

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

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

因此,實現 CORS 通信的關鍵是伺服器。只要伺服器實現了 CORS 介面,就可以跨源通信。

瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。

只要同時滿足以下兩大條件,就屬於簡單請求

  1. 請求方法是以下三種方法之一:
  • HEAD
  • GET
  • POST
  1. HTTP的頭信息不超出以下幾種欄位:
  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限於三個值 application/x-www-form-urlencoded、multipart/form-data、text/plain

凡是不同時滿足上面兩個條件,就屬於非簡單請求

瀏覽器對這兩種請求的處理,是不一樣的。

簡單請求

  1. 在請求中需要附加一個額外的 Origin 頭部,其中包含請求頁面的源信息(協議、功能變數名稱和埠),以便伺服器根據這個頭部信息來決定是否給予響應。例如:Origin: http://www.laixiangran.cn
  2. 如果伺服器認為這個請求可以接受,就在 Access-Control-Allow-Origin 頭部中回發相同的源信息(如果是公共資源,可以回發 * )。例如:Access-Control-Allow-Origin:http://www.laixiangran.cn
  3. 沒有這個頭部或者有這個頭部但源信息不匹配,瀏覽器就會駁回請求。正常情況下,瀏覽器會處理請求。註意,請求和響應都不包含 cookie 信息。
  4. 如果需要包含 cookie 信息,ajax 請求需要設置 xhr 的屬性 withCredentials 為 true,伺服器需要設置響應頭部 Access-Control-Allow-Credentials: true

非簡單請求

瀏覽器在發送真正的請求之前,會先發送一個 Preflight 請求給伺服器,這種請求使用 OPTIONS 方法,發送下列頭部:

  • Origin:與簡單的請求相同。
  • Access-Control-Request-Method: 請求自身使用的方法。
  • Access-Control-Request-Headers: (可選)自定義的頭部信息,多個頭部以逗號分隔。

例如:

Origin: http://www.laixiangran.cn
Access-Control-Request-Method: POST
Access-Control-Request-Headers: NCZ

發送這個請求後,伺服器可以決定是否允許這種類型的請求。伺服器通過在響應中發送如下頭部與瀏覽器進行溝通:

  • Access-Control-Allow-Origin:與簡單的請求相同。
  • Access-Control-Allow-Methods: 允許的方法,多個方法以逗號分隔。
  • Access-Control-Allow-Headers: 允許的頭部,多個方法以逗號分隔。
  • Access-Control-Max-Age: 應該將這個 Preflight 請求緩存多長時間(以秒錶示)。

例如:

Access-Control-Allow-Origin: http://www.laixiangran.cn
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: NCZ
Access-Control-Max-Age: 1728000

一旦伺服器通過 Preflight 請求允許該請求之後,以後每次瀏覽器正常的 CORS 請求,就都跟簡單請求一樣了。

優點

  • CORS 通信與同源的 AJAX 通信沒有差別,代碼完全一樣,容易維護。
  • 支持所有類型的 HTTP 請求。

缺點

  • 存在相容性問題,特別是 IE10 以下的瀏覽器。
  • 第一次發送非簡單請求時會多一次請求。

JSONP 跨域

由於 script 標簽不受瀏覽器同源策略的影響,允許跨域引用資源。因此可以通過動態創建 script 標簽,然後利用 src 屬性進行跨域,這也就是 JSONP 跨域的基本原理。

直接通過下麵的例子來說明 JSONP 實現跨域的流程:

// 1. 定義一個 回調函數 handleResponse 用來接收返回的數據
function handleResponse(data) {
    console.log(data);
};

// 2. 動態創建一個 script 標簽,並且告訴後端回調函數名叫 handleResponse
var body = document.getElementsByTagName('body')[0];
var script = document.gerElement('script');
script.src = 'http://www.laixiangran.cn/json?callback=handleResponse';
body.appendChild(script);

// 3. 通過 script.src 請求 `http://www.laixiangran.cn/json?callback=handleResponse`,
// 4. 後端能夠識別這樣的 URL 格式並處理該請求,然後返回 handleResponse({"name": "laixiangran"}) 給瀏覽器
// 5. 瀏覽器在接收到 handleResponse({"name": "laixiangran"}) 之後立即執行 ,也就是執行 handleResponse 方法,獲得後端返回的數據,這樣就完成一次跨域請求了。

優點

  • 使用簡便,沒有相容性問題,目前最流行的一種跨域方法。

缺點

  • 只支持 GET 請求。
  • 由於是從其它域中載入代碼執行,因此如果其他域不安全,很可能會在響應中夾帶一些惡意代碼。
  • 要確定 JSONP 請求是否失敗並不容易。雖然 HTML5 給 script 標簽新增了一個 onerror 事件處理程式,但是存在相容性問題。

圖像 Ping 跨域

由於 img 標簽不受瀏覽器同源策略的影響,允許跨域引用資源。因此可以通過 img 標簽的 src 屬性進行跨域,這也就是圖像 Ping 跨域的基本原理。

直接通過下麵的例子來說明圖像 Ping 實現跨域的流程:

var img = new Image();

// 通過 onload 及 onerror 事件可以知道響應是什麼時候接收到的,但是不能獲取響應文本
img.onload = img.onerror = function() {
    console.log("Done!");
}

// 請求數據通過查詢字元串形式發送
img.src = 'http://www.laixiangran.cn/test?name=laixiangran';

優點

  • 用於實現跟蹤用戶點擊頁面或動態廣告曝光次數有較大的優勢。

缺點

  • 只支持 GET 請求。
  • 只能瀏覽器與伺服器的單向通信,因為瀏覽器不能訪問伺服器的響應文本。

伺服器代理

瀏覽器有跨域限制,但是伺服器不存在跨域問題,所以可以由伺服器請求所要域的資源再返回給客戶端。

伺服器代理是萬能的。

document.domain 跨域

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

比如,有一個頁面,它的地址是 http://www.laixiangran.cn/a.html,在這個頁面裡面有一個 iframe,它的 src 是 http://laixiangran.cn/b.html。很顯然,這個頁面與它裡面的 iframe 框架是不同域的,所以我們是無法通過在頁面中書寫 js 代碼來獲取 iframe 中的東西的。

這個時候,document.domain 就可以派上用場了,我們只要把 http://www.laixiangran.cn/a.htmlhttp://laixiangran.cn/b.html 這兩個頁面的 document.domain 都設成相同的功能變數名稱就可以了。但要註意的是,document.domain 的設置是有限制的,我們只能把 document.domain 設置成自身或更高一級的父域,且主域必須相同。例如:a.b.laixiangran.cn 中某個文檔的 document.domain 可以設成 a.b.laixiangran.cnb.laixiangran.cnlaixiangran.cn 中的任意一個,但是不可以設成 c.a.b.laixiangran.cn ,因為這是當前域的子域,也不可以設成 baidu.com,因為主域已經不相同了。

例如,在頁面 http://www.laixiangran.cn/a.html 中設置document.domain:

<iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()">
<script>
    document.domain = 'laixiangran.cn'; // 設置成主域
    function test() {
        console.log(document.getElementById('myIframe').contentWindow);
    }
</script>

在頁面 http://laixiangran.cn/b.html 中也設置 document.domain,而且這也是必須的,雖然這個文檔的 domain 就是 laixiangran.cn,但是還是必須顯式地設置 document.domain 的值:

<script>
    document.domain = 'laixiangran.cn'; // document.domain 設置成與主頁面相同
</script>

這樣,http://www.laixiangran.cn/a.html 就可以通過 js 訪問到 http://laixiangran.cn/b.html 中的各種屬性和對象了。

window.name 跨域

window 對象有個 name 屬性,該屬性有個特征:即在一個視窗(window)的生命周期內,視窗載入的所有的頁面(不管是相同域的頁面還是不同域的頁面)都是共用一個 window.name 的,每個頁面對 window.name 都有讀寫的許可權,window.name 是持久存在一個視窗載入過的所有頁面中的,並不會因新頁面的載入而進行重置。

通過下麵的例子介紹如何通過 window.name 來跨域獲取數據的。

頁面 http://www.laixiangran.cn/a.html 的代碼:

<iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()" style="display: none;">
<script>
    // 2. iframe載入 "http://laixiangran.cn/b.html 頁面後會執行該函數
    function test() {
        var iframe = document.getElementById('myIframe');
        
        // 重置 iframe 的 onload 事件程式,
        // 此時經過後面代碼重置 src 之後,
        // http://www.laixiangran.cn/a.html 頁面與該 iframe 在同一個源了,可以相互訪問了
        iframe.onload = function() {
            var data = iframe.contentWindow.name; // 4. 獲取 iframe 里的 window.name
            console.log(data); // hello world!
        };
        
        // 3. 重置一個與 http://www.laixiangran.cn/a.html 頁面同源的頁面
        iframe.src = 'http://www.laixiangran.cn/c.html';
    }
</script>

頁面 http://laixiangran.cn/b.html 的代碼:

<script type="text/javascript">
    // 1. 給當前的 window.name 設置一個 http://www.laixiangran.cn/a.html 頁面想要得到的數據值 
    window.name = "hello world!";
</script>

location.hash 跨域

location.hash 方式跨域,是子框架具有修改父框架 src 的 hash 值,通過這個屬性進行傳遞數據,且更改 hash 值,頁面不會刷新。但是傳遞的數據的位元組數是有限的。

頁面 http://www.laixiangran.cn/a.html 的代碼:

<iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()" style="display: none;">
<script>
    // 2. iframe載入 "http://laixiangran.cn/b.html 頁面後會執行該函數
    function test() {
        // 3. 獲取通過 http://laixiangran.cn/b.html 頁面設置 hash 值
        var data = window.location.hash;
        console.log(data);
    }
</script>

頁面 http://laixiangran.cn/b.html 的代碼:

<script type="text/javascript">
    // 1. 設置父頁面的 hash 值
    parent.location.hash = "world";
</script>

postMessage 跨域

window.postMessage(message,targetOrigin) 方法是 HTML5 新引進的特性,可以使用它來向其它的 window 對象發送消息,無論這個 window 對象是屬於同源或不同源。這個應該就是以後解決 dom 跨域通用方法了。

調用 postMessage 方法的 window 對象是指要接收消息的那一個 window 對象,該方法的第一個參數 message 為要發送的消息,類型只能為字元串;第二個參數 targetOrigin 用來限定接收消息的那個 window 對象所在的域,如果不想限定域,可以使用通配符 *。

需要接收消息的 window 對象,可是通過監聽自身的 message 事件來獲取傳過來的消息,消息內容儲存在該事件對象的 data 屬性中。

頁面 http://www.laixiangran.cn/a.html 的代碼:

<iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()" style="display: none;">
<script>
    // 1. iframe載入 "http://laixiangran.cn/b.html 頁面後會執行該函數
    function test() {
        // 2. 獲取 http://laixiangran.cn/b.html 頁面的 window 對象,
        // 然後通過 postMessage 向 http://laixiangran.cn/b.html 頁面發送消息
        var iframe = document.getElementById('myIframe');
        var win = iframe.contentWindow;
        win.postMessage('我是來自 http://www.laixiangran.cn/a.html 頁面的消息', '*');
    }
</script>

頁面 http://laixiangran.cn/b.html 的代碼:

<script type="text/javascript">
    // 註冊 message 事件用來接收消息
    window.onmessage = function(e) {
        e = e || event; // 獲取事件對象
        console.log(e.data); // 通過 data 屬性得到發送來的消息
    }
</script>

參考資料


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 二台sql伺服器配置ISCSI虛擬磁碟 在上篇我們利用ISCSI Target軟體在DC-ISCSCI上創建了三個ISCSI虛擬磁碟,在下麵我們將為大家介紹SQL-CL01(hsr1 50)和SQL-CL02(hsr2 51)如何訪問這些ISCSI虛擬磁碟. SQL-CL01和SQL-CL02可以使 ...
  • 系統資料庫: 1、master:用於記錄所有SQL server系統級別的信息,比如說登錄的信息,系統設置信息。如果他沒了。你的SQL server就掛了。 2、model:模型的意思,就像模板一樣。創建資料庫都是繼承model。如果你在該資料庫下創建一張表,以後每創建新資料庫,就已經有了model ...
  • 虛擬磁碟創建 前面我們已經搭了域和兩台sql 伺服器, 下麵我們準備讓DC域伺服器除了擔當域控制器外,還行使另一個職能:充當集群共用存儲。 集群共用存儲是由群集內的每個節點都能共同訪問的一個存儲設備,本次實驗中準備用DC伺服器來模擬存儲設備。 第一步下載iscsi target程式, 放在dc 桌面 ...
  • 遍歷一個目錄下的所有文件 首先我們獲取用戶文檔目錄路徑 1 let manager = FileManager.default 2 let urlForDocument = manager.urls(for: .documentDirectory, in:.userDomainMask) 3 let ...
  • 枚舉、結構體和協議組成Model 1 //定義一個協議 2 protocol BaseItemProtocal { 3 var title: String { get set } //屬性是可讀可寫的 4 var type: CriteriaType { get set } 5 } 6 7 stru ...
  • 眾所周知,app的一些功能可能會使用到H5開發,這就難免會遇到java與js 的相互調用,android 利用WebViewJavascriptBridge 實現js和java的交互,這裡介紹下JsBridge第三方庫的使用。 github傳送門:https://github.com/lzyzsd/ ...
  • 前沿 首先OkHttp3是支持Gzip解壓縮的,不過我們要明白,它是支持我們在發起請求的時候自動加入header,Accept-Encoding: gzip,而我們的伺服器返回的時候header中有Content-Encoding: gzip。 關於更多深入的內容呢,可以參考閱讀下麵這篇文章,講的非 ...
  • 看到很多小伙伴對OkHttp的緩存問題並不是十分瞭解,於是打算來說說這個問題。用好OkHttp中提供的緩存,可以幫助我們更好的使用Retrofit、Picasso等配合OkHttp使用的框架。OK,廢話不多說,我們來看看OkHttp中的緩存。 OkHttp中的緩存整體上來說我們要在兩個地方配置,一個 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...