瀏覽器限制一個源的文件(document)或它載入的指令碼(script)如何與另一個源的資源進行互動。它能幫助阻隔惡意文件,減少可能被攻擊的媒介。但是這樣也造成問題:跨域。在前後端分離的專案中,前端傳送請求給後臺伺服器,後臺伺服器接收到請求,處理完響應給前端。但是由於瀏覽器的同源策略(Same-origin policy),該響應被攔截了。
什麼是同源
要了解同源,先來看看 URL 是怎麼組成的:
protocol://[host]:[port][path][?query][anchor]
e.g.
http://www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument
其中:
protocol
為協議,這裡為http
host
為域名,這裡為www.example.com
。其中example.com
為主域名,www.example.com
為子域名。port
為埠,這裡為80
path
為資源路徑,這裡為/path/to/myfile.html
query
為查詢字串,這裡為?key1=value1&key2=value2
anchor
為描點,這裡為#SomewhereInTheDocument
在 URL 中,只要 protocol
, host
和 path
相同,則認定為同源。
例如,一個 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 |
失敗 | 主機不同 |
http://company.com/dir/other.html |
失敗 | 主機不同,store 為 company.com 的一個子域名。 |
同源策略
瀏覽器認為網際網路是不可信的,於是它引入了同源策略,拒絕非同源的請求。
瀏覽器共限制三種行為:
Cookie、LocalStorage 和 IndexDB 只能同源讀取。
其中,Cookie 的同源策略有點不同。可以為父域(e.g. example.com)設定 Cookie,那樣其子域(e.g. www.example.com, abc.example.com)都共享該 Cookie。當然也可以只為子域設定 Cookie,這樣該 Cookie 只能子域訪問。
這個比較好理解,自己源的資料只能自己讀取。要是別人都能讀取,那資訊不久全部洩露了。
DOM 只能同源獲得。
也就是兩個不同源的頁面之間無法對方的
DOM
元素。如果該限制取消了,就會造成安全隱患。最經典就是iframe
的例子:你在某處開啟了這個網站
https://www.bilibil.com/
,這個網站看起來和嗶哩嗶哩沒有任何區別,但是仔細觀察它並不是官方網站。黑客使用 iframe 載入真正的嗶哩嗶哩網站,並將iframe
佈局調整全屏,看起來就和真正的嗶哩嗶哩沒有任何區別。而你也沒有察覺這樣的問題,於是你就登入你的賬號。如果沒有DOM
同源策略,黑客就可以從https://www.bilibil.com/
網站直接抓取輸入框的DOM
,從而拿到你的賬號和密碼。XMLHttpRequest 只能同源請求。
XMLHttpRequest 也就是我們常說的 AJAX。AJAX 的同源可以免受瀏覽器遭受到 CSRF 攻擊(並不能抵擋全部的 CSRF 攻擊)。
這裡主要討論同源策略中不同源之間的請求、互動限制。
同源策略中對不同源之間的互動限制,比如主要分為三類:
- 跨域寫操作(Cross-origin writes) 一般是被允許的。例如,連結(links),重定向以及表單提交。
- 跨域資源嵌入(Cross-origin embedding) 一般是被允許。例如,一系列可以通過
src
引入資源的標籤:script
、img
、video
、audio
等;通過@font-face
引入的字型;通過iframe
載入的任何資源。 - 跨域讀操作(Cross-origin reads) 一般是不被允許的。例如,AJAX。
解決方案
解決跨域,一般就是解決不同源之間可以互相請求 AJAX,不同源之間可以互相獲取 DOM 節點。
AJAX 解決方案
在說明解決方案之前,來看看瀏覽器是如何阻止 AJAX 跨域的。
- 我們在當前網站中請求一個非同域的 API。
- 瀏覽器就會給該伺服器傳送請求。
- 伺服器接收到請求後,處理過後成功響應。
- 瀏覽器接受到這個響應,發現該請求為跨域響應,於是拒絕了該響應。
從上面得出結論,瀏覽器並不是不能傳送跨域請求,而是拒絕了跨域響應。
這也是我們在請求非同源網站時,瀏覽器會返回 net::ERR_FAILED
並告訴我們並沒有設定 Access-Control-Allow-Origin
這個頭資訊。
解決方案一:CORS
經過多年的發展,前後端分離已成為業界標杆。既然是前後端分離就要處理跨域問題。
我們知道,在伺服器返回響應給瀏覽器後,瀏覽器是拒絕了該響應。所以 W3C 就推出一個標準:CORS
。它是通過設定一系列的響應頭來解決跨域問題的。
最重要的響應頭當然是 Access-Control-Allow-Origin
,它可以設定成 *
代表允許所有網站的請求跨域。也可以設定成 URI,只允許指定的網站請求跨域。
這裡列舉常用的響應頭,更詳細的內容可以檢視 MDN 的文件:
Access-Control-Allow-Origin
請求資源能共享給那些域Access-Control-Allow-Credentials
是否允許傳送 CookieAccess-Control-Allow-Methods
對預請求的響應中,指示實際的請求中可以使用哪些 HTTP 頭。Access-Control-Max-Age
預請求結果能被快取多久,單位為秒。-1 表示禁用快取。
因為是響應,所以 CORS 的解決方案在後端進行。
對了,CORS 分為 簡單請求 和 非簡單請求 這裡不過多贅述,詳情請看文件
解決方案二:代理伺服器
我們知道同源策略是瀏覽器上的限制,伺服器之間是沒有跨域這個說法的。
所以可以加個中間層來解決這個問題:
- 瀏覽器的請求發到同源代理伺服器。
- 同源代理伺服器收到請求後進行轉發,轉發到不同源的伺服器。以此進行資料互動。
- 資料互動之後,先傳送到同源代理伺服器,再轉發到瀏覽器。
整個過程瀏覽器只和同源代理伺服器交流,故解決跨域。
解決方案三:JSONP
JSONP 可以堪稱是跨域解決的奇淫技巧,因為它是利用 <script>
元素的「漏洞」來實現的。
從上面我們可以知道,<script>
標籤是可以引用其他域的 js 指令碼,該 js 指令碼載入成功後會觸發回撥函式,該回撥函式裡面就有我們想要的資料。
使用 JSONP 的好處為瀏覽器相容性好,但它只能支援 GET 請求。
解決方案四:WebSocket
WebSocket 和 HTTP 一樣都是應用層的協議,與 HTTP 不一樣的是,它並沒有同源策略。只要伺服器支援該協議,就可以實現跨域通訊。
獲取 DOM 解決方案
我們知道 iframe
和 window.open
開啟的視窗,他們與父視窗之前無法通訊。父視窗無法獲取子視窗的 DOM,反之亦然。
document.domain
通過 document.domain
這個屬性值,如果兩個視窗的一級域名相同,則可以施行視窗之間的通訊。同樣可以通訊的還有 Cookie 值。
片段識別符號(fragment identifier)
片段識別符號指的是 URL 後面 #
號後面的部分,也成為錨(anchor)。
如果只是改變片段識別符號的內容,頁面就不會重新重新整理,從而實現跨域。
window.name
windows.name 是瀏覽器視窗的一個屬性。該屬性,無論是否同源,只要在同一個視窗裡,前一個網頁設定了這個屬性,後一個網頁可以讀取它。
window.postMessage
上面兩種方法都是利用的漏洞,HTML 5 引入跨文件通訊 API(Cross-document messaging)來解決這個問題。
這個 API 為 window
物件新增了一個 window.postMessage
方法,允許跨視窗通訊,不論這兩個視窗是否同源。
它可以解決一下方面的問題:
- 頁面和其開啟的新視窗的資料傳遞
- 多視窗之間訊息傳遞
- 頁面與巢狀的iframe訊息傳遞
- 上面三個場景的跨域資料傳遞
window.postMessage 這個方法非常強大,詳情請參考 文件。
參考連結
- developer.mozilla.org/zh-CN/docs/W...
- developer.mozilla.org/zh-CN/docs/L...
- juejin.cn/post/6879360544323665928
- www.ruanyifeng.com/blog/2016/04/sa...
- developer.mozilla.org/zh-CN/docs/W...
- www.ruanyifeng.com/blog/2016/04/sa...
本作品採用《CC 協議》,轉載必須註明作者和本文連結