1. 同源與跨域
1.1 基調
一般情況下,禁止一個域從另一個域讀取數據,卻可以使用某些從其他域拿到的資源。比如說,允許一個域執行、渲染、應用從其他域獲取到的腳本、圖片、樣式;同樣,一個域可以展示從其他域獲取的內容,比如在 frame 中顯示 html 文檔。網絡資源也可以選擇性的讓其他域來讀取自己的信息,比如使用 Cross-Origin Resource Sharing,這種情況下訪問權是針對單個域授權的。
同源策略限制消息從一個域發送到另一個域。比如說同源策略允許域間的 GET 和 POST 方式的 HTTP 請求,卻禁止域間的 PUT 和 DELETE 方式的請求。同時,域在發送請求到自己時可以自定義 HTTP 請求頭,發送請求到其他域不能自定義請求頭。
同源策略的控制者是瀏覽器,瀏覽器可以控制不同域之間的資源的訪問或相互操作,但不控制自己對不同域之間的資源的操作和訪問。
1.2 什麼是源
RFC6454 規定一個資源的源由資源的 URI 中的(協議,主機,端口)這一個三元組來確定(IE 中沒有把端口納入源的屬性)。比如 https://www.test.com/test-script.js, 這個資源的源爲(https, www.test.com, 80)。
對於一個被執行的腳本來說,他的源屬性是執行這個腳本的資源的源屬性。比如一個頁面加載了來自另一個域的腳本,這個腳本執行的時候向頁面所在域發送請求被視爲同源。
相對路徑和無法明確源屬性的協議(javascript:,data:,about:blank)的資源的源,取將這些資源載入的頁面的源。
IE 沒有將端口作爲同源的組成部分,原因是 IE 歷史壟斷的市場佔有率導致的歷史遺留問題,IE8 嘗試在原生的 XMLHTTPRequest 中使用端口作爲同源的一部分(標準化),但效果不好,一些依照老 IE 特性開發的站點,爲了保持兼容性,繼續使用了原有的 MSXML 方式的 XMLHTTPRequest。所以在 IE8-11 的版本都沒有考慮端口作爲同源的判斷條件。
1.3 怎樣算同源
兩個資源的組成源的屬性(協議,主機,端口),完全一致,這兩個資源才同源。這裏如果兩個資源的協議分別爲 http 和 https 被認爲不同源。同時,即使兩個資源的所屬同一臺服務器,但給出的域名分別爲 www.testA.com 和 www.testB.com,這兩個資源也被認爲是不同源。
1.4 同源策略
同源策略是瀏覽器的核心安全策略,目的是將來自不同源的資源進行隔離,並控制不同源資源間的通信,從而減少安全威脅,增強安全性。
1.4.1 同源策略的規則
a. 不限制
互聯網的核心思想是資源共享,資源的相互訪問應該被允許。
- 執行來自其他域的腳本(如使用
<script>
標籤引用 CDN 的腳本,JSONP 請求等); - 渲染來自其他域的圖片(如使用
<img>
標籤引用圖片服務器的圖片資源); - 應用來自其他域的樣式(如使用
<link>
標籤引用來自靜態資源服務器的樣式文件); - 嵌入來自其他域的資源(使用
<iframe>
,<frame>
加載來自其他域的資源); - 重定向頁面地址(
Location
對象地址的改變,使用<a>
鏈接到其他資源); - 數據發送(使用
<form>
向其他域提交數據); - 多個子域的資源可以設置
document.domain
來改變所屬域屬性,來實現子域同域; window.name
屬性是可寫的,腳本可以隨意設置,iframe
的name
屬性,會作爲iframe
內部window
對象的name
屬性初始值;- 窗體層級嵌套可以通過
parent.parent...
的方式來訪問祖先窗口,不受跨域限制; postMessage
接口跨域通信;<video>
、<audio>
、<object>
、<embed>
、<applet>
、@font-face
可以加載其他域的資源。
b. 部分限制
- 跨域發送請求不能自定義 HTTP Header;
- 跨域發送請求不能使用 PUT 和 DELETE 方式,只能使用 GET 和 POST;
- 腳本可以訪問一個不同源窗口的整體,而不能訪問窗口的內部信息;
- CORS 可以改變跨域的情形
Access-Control-Allow-Origin
,同源策略不放寬,跨域請求正常工作(設置爲例外),不包含用戶名和密碼,不包含cookie
和token
,響應的cookie
會被丟棄,如果需要這些信息,需要設置XMLHttpRequest
的withCredentials=true
。
c. 完全限制
- 限制本地文件系統讀寫;
- 限制 cookie 的訪問;
- 限制 FileUpload 元素的 value 屬性,不能修改,甚至不能讀取路徑;
- 限制腳本對來自不同服務器的文檔的讀寫(同源策略);
- 限制本地存儲 localStorage 和 sessionStorage 和 IndexedDB;
- 限制 XMLHTTPRequest 請求的發送對象。
1.5 跨域
同源策略將來自不同源的資源進行了隔離,略在阻止安全威脅的同時,也對正常的訪問帶來了不便,我們也需要在安全策略下根據應用的需要進行不同源資源間的通信。 比如和 iframe 或 frame 中的資源的通信,與新窗口的資源的通信,與不同域的服務器之間的通信等。
比如同源策略限制了不同源的資源之間的互操作,當頁面在 iframe 中加載不同源的內容的時候,想要根據頁面內容動態調整 iframe 的高度的時候,就需要特殊的跨域操作。
在應用系統中做單點登錄的時候,在進行當前應用的認證的時候需要向認證中心進行認證,這個時候也需要特殊的操作。
2. 跨域方法彙總
跨域的方法和瀏覽器安全問題都圍繞着同源策略來展開,我們可以避開瀏覽器端的參與,從而規避同源策略帶來的不便;同時我們也可以利用同源策略及其輔助接口開放的功能特性來實現跨越通信。
2.1 惹不起躲得起
如果可以的話,可以將 Web 應用部署在同一個域下,這樣可以很好的迴避跨域的問題,我們常用的通過本域的後端接口包裝,避免跨域的問題。
2.2 使用反向代理
直接使用 Web 服務器 apache,nginx 等的反向代理的方式,將需要跨域的請求發送到當前域,在 Web 容器配置中做請求轉發,像 nginx 這樣的反向代理很擅長做這樣的事情。
2.3 直面慘淡的人生
2.3.1 動態不受限標籤
使用腳本動態創建<script>
、<img>
、<link>
、<iframe>
、<frame>
等標籤,在加入文檔 DOM 後,瀏覽器會自動加載並解析渲染響應的資源。
2.3.2 JSONP
JSONP 原理其實是對<script>
標籤的一個利用,首先<script>
標籤加載資源是不受域限制的,然後瀏覽器會將<script>
加載的內容當做腳本來執行。如果服務端返回的是類似於callback({"data":[{"aa":1}]})
這樣的內容,那麼瀏覽器會將{"data":[{"aa":1}]}
作爲函數參數調用callback
這個方法。這樣就實現了從不同域加載數據。
2.3.3 Form 提交
Form 提交不受限制,客戶端可以以 GET 和 POST 的方式向服務端提交數據。
2.3.4 document.domain
每一個窗體可以對當前窗體所屬域進行微調,比如當前與名爲app.test.com
,則可以設置document.domain
爲test.com
,也可以設置爲app.test.com
。通過將子域的document.domain
屬性均改爲主域test.com
,可以實現test.com
下的任意子域app.test.com
、auth.test.com
、img.test.com
等之間的通信。
修改爲主域之後,子域的訪問會帶上父域的cookie
,反之則不然 .test.com
和 test.com
效果一樣,寫成 test.com
瀏覽器會理解爲.test.com
。
2.3.5 window.name
window.name
在加載不同的頁面後還會存在,可以通過使用同一個window
來加載需要通信的頁面,通過共享window.name
來進行數據通信。
2.3.6 CORS(Cross Origin Resource Sharing)
通過協商的的 HTTP Header 讓瀏覽器和服務端進行通信,來決定請求或者響應是否有效。
默認情況下,瀏覽器發送跨域請求不帶認證信息(比如 cookie、證書、代理認證信息等),withCredentials
屬性值爲false
跨域需要withCredentials=true
,同時服務端允許Access-Control-Allow-Credentials:true
,同時Access-Control-Allow-Origin
值不能爲*
。
2.3.7 postMessage
這個是 HTML5 新增的頁面間通信的接口,能夠很好的解決 iframe 之間通信的問題。
2.4 Fetch
2.4.1 帶認證信息信息跨域
- 請求設置
credentials:true
; - 響應設置
Access-Control-Allow-Origin:http://origin.to.cross,Access-Control-Allow-Credentials:true
。
2.4.2 請求對象
mode
值爲 same-origin,cors,no-cors(默認),navigate,websocket- credentials mode 值爲 omit(默認),same-origin,include
2.4.3 響應對象
- 同域響應 type 值爲 basic,cors,default(默認),error
- 跨域響應 type 值爲 opaque,opaqueredirect,error
2.4.4 WebSocket
這也是 HTML5 新增的瀏覽器和服務端通信的非 HTTP 通信的機制,它不受同源策略的限制,是解決跨域數據傳輸的解決方案。
- 在 https 的頁面,無法發送 ws://的請求,同 http
3. 不同角度看問題
3.1 本地頁面間通信 VS Browser-Server 通信
3.1.1 本地頁面間通信
- 動態標籤
- postMessage
- window.name
- document.domain
3.1.2 Browser-Server 通信
GET
- 動態標籤
- JSONP
- Form 提交
- CORS
- WebSocket
- fetch
POST
- Form 提交
- CORS
- WebSocket
- fetch
3.2 單向通信 VS 雙向通信
單向通信
- 動態標籤
- JSONP
- Form 提交
- CORS
- fetch
雙向通信
- window.name
- document.domain
- postMessage
- WebSocket
3.3 前端單獨處理 VS 需要後端配合
前端單獨處理
- 動態標籤
- window.name
- document.domain
- postMessage
- fetch
需要後端配合
- JSONP
- Form 提交
- CORS
- WebSocket
3.4 全版本瀏覽器 VS 現代瀏覽器
全版本瀏覽器
- 動態標籤
- JSONP
- Form 提交
- window.name
- document.domain
現代瀏覽器
- 動態標籤
- JSONP
- Form 提交
- window.name
- document.domain
- CORS
- postMessage
- WebSocket
- fetch
參考資料
- RFC 6454 The Web Origin Concept
- W3C 同源策略
- MDN CORS
- 《Web 之困-現代 Web 應用安全指南》
本文來自 Qunar 技術沙龍
作者:張釕,去哪兒網旅遊度假事業部前端開發工程師,主體是 JavaScript 和 Node 相關技術;搞過 Java、PHP、Python 相關的 Web 開發,混過安全圈;對性能調優和安全攻防感興趣,側重基礎理論(現階段是操作系統和編譯器原理),專注效率改善,讓一切技術高效合理。