前言
跨域這兩個字就像一塊狗皮膏藥一樣黏在每一個前端開發者身上,無論你在工作上或者面試中無可避免會遇到這個問題。如果在網上搜索跨域問題,會出現許許多多方案,這些方案有好有壞,但是對於闡述跨域的原理和在什麼情況下需要用什麼方案,缺少系統性的說明。大家在工作中可能因爲大佬們已經配置好了,不會產生跨域,但是作爲一個前端的開發人員,面對跨域的問題,還是需要從原理上去理解跨域的原因,在不同的情況中,我們該如何去處理。
1 業務場景
1.1 介紹
WMS6.0是一款專門爲倉儲業務打造的合作開發平臺,前臺BP可以獨立開發或者定製現有的流程,接入到WMS6.0中,實現自定義業務,使前臺BP只需要關注自己的業務,不用專注其他功能,提升前臺BP的開發效率。。
作爲一個合作平臺,WMS6.0 PC端支持獨立頁面擴展和頁面內部功能擴展,支持前臺BP可以進行獨立部署,實現最大程度的解耦。接入方案如下:
- 獨立頁面擴展,以完全獨立業務模塊的方式接入。針對部分合作方需要自己完全獨立開發頁面的情況,WMS6.0提供了微前端的框架進行接入。
- 頁面內部功能擴展,以預留插槽的方式接入。如圖1中標註部分所示,整體頁面被劃分爲多個區域,其中包含了通用的數據模塊 + bp接入模塊。當合作方有個性化的數據統計需求時,可以進行獨立開發,然後接入現有公用頁面中。
在bp接入平臺的過程中,我們遇到了各種各樣的問題,如前後端如何聯調、如何在不衝突的情況下自定義全局屬性、如何部署上線等等,下面我們主要就前後端聯調中遇到的跨域問題進行討論。
在使用上述預留插槽的接入方式時,爲了通用模塊與接入模塊之間的數據同步等方便進行,WMS6.0中並沒有使用老式的iframe,而是採用了vue註冊的方式,實現在同一個頁面中加載。因此合作方在獨立模塊中發起的服務端請求,其來源其實仍是當前通用頁面。
而WMS6.0並不能確保所有的合作方服務端均在同一個域名下,由此也就產生了各種交互問題。
1.2 wms6.0請求鏈路
我們先來看一下WMS6.0現有的通用網絡請求整體鏈路。
當用戶觸發了網絡請求,會通過基站或者倉庫的路由發出,然後通過網絡到達物流網關,物流網關把請求轉發到Nginx,Nginx會把請求分發到具體的服務器上進行數據處理。
下面我們就抽取一個WMS6.0通過物流網關訪問的請求,作爲實例來看一下。
通過response Headers(相應頭)我們可以看到,公司現有的物流網關會對指定域名的頁面進行CORS跨域處理。通過Access-Control-Allow-Origin: http://a..com,我們可以知道物流網關可以接受來自指定域 http://a..com 的跨域資源請求,不會產生跨域報錯。
但是咱們部分bp合作方的接口並不是通過物流網關的,這就需要我們自己對此類接口進行跨域處理了。假如沒有進行跨域處理,那麼就會報下面的錯了。
1.3 跨域的產生
- Access to XMLHttpRequest at '' from origin '' has been blocked by CORS policy
- Response to preflight request doesn't pass access control check
- No 'Access-Control-Allow-Origin' header is present on the requested resource.
報錯解析:
從源“本地路徑”訪問 “目標路徑(請求鏈接)”的文本傳輸請求已被CORS策略阻塞:對預檢請求的響應未通過訪問控制檢查。請求的資源上不存在'Access- control - allow - origin '報頭。
錯誤原因:
本地路徑和目標路徑不在同一個域名下引起的跨域問題。
同時需要注意的是,就算兩個域名是同一個二級域名、不同三級域名的時候,例如 a.baidu.com 和 b.baidu.com ,也是屬於不同域的,仍會出現這個問題。
那麼到底什麼是跨域,跨域既然影響了我們的開發工作,那又爲什麼要有對跨域的限制呢?下面讓我們來了解一下跨域的歷史產生原因和作用吧。
2 跨域
2.1 演變史
以下內容爲個人猜測,僅供參考,勿噴 🤞
- 第一階段
互聯網始於1969年的美國。在互聯網的最早期,美軍在ARPA(阿帕網,美國guofang部研究計劃署)制定的協定下,首先用於軍事連接。
隨後主要都是美國高校連入的網絡,如美國西南部的加利福尼亞大學洛杉磯分校、斯坦福大學研究學院、UCSB(加利福尼亞大學)和猶他州大學的四臺主要的計算機。服務器上存放的都是公開資料。
這個時候網站更像是一個公共圖書館,賬戶密碼都沒有,更沒存放着什麼機密資料
- 第二階段
後來,有人覺得可以在上面放一些私人資料,私人信息。於是爲了安全,便有了賬戶和密碼。可是如果每次訪問都需要輸入賬戶和密碼,是一件很煩的事情。
所以瀏覽器實現了cookie,用來存儲用戶登陸的賬戶和密碼。當用戶訪問了曾經已經登陸過的網站,瀏覽器將會自動在請求中加入賬戶和密碼,而賬戶和密碼通常是通過 Request Header(請求頭) 中的cookie或指定的頭信息進行通信的。
而直接存儲賬戶和密碼太過於危險,如果被攻破,損失相當大。所以瀏覽器都不直接存儲賬戶和密碼,而是存儲登陸令牌。
- 第三階段 - 現代瀏覽器同源策略
但是存儲登陸令牌也有一個問題,如果你登陸了某個流氓網站,同時這個流氓網站在它的JS裏訪問了你已經登錄的其他網站,那麼就能夠拿到你已經登錄的其他網站裏面的一些重要數據。
所以瀏覽器爲了安全是不能夠讓這個流氓網站訪問你已經登錄的其他網站的。由此產生了瀏覽器的同源策略:哪裏來的,就只能訪問哪裏的數據。
綜上,我們就可以基本瞭解對跨域的定義了,如下:
2.2 定義
跨域是指向一個與當前頁面所在域不同的目標地址發送請求的過程,這樣之所以會產生跨域報錯是因爲瀏覽器的同源策略限制。看起來同源策略影響了我們開發的順暢性。實則不然,同源策略存在的必要性之一是爲了隔離攻擊。
MDN上對同源策略的解釋爲:
同源策略限制了從同一個源加載的文檔或腳本如何與來自另一個源的資源進行交互。這是一個用於隔離潛在惡意文件的重要安全機制。
下面就拿同源策略隔離的主要攻擊之一CSRF爲例講述下同源策略存在的必要性:
2.3 舉例說明重要性-跨站請求僞造(CSRF)
CSRF,cross-site request forgery,又稱跨站請求僞造,指非法網站挾持用戶cookie在已登陸網站上實施非法操作的攻擊,這是基於部分頁面使用cookie在網站免登和用戶信息留存。
正常網站免登的請求流程如下:
- 我們進入一個網站,發送登陸請求給後端
- 後端接受登陸請求,判斷登陸信息是否準確
- 判斷信息準確後,後端會發送response給瀏覽器
- 瀏覽器接受response返給用戶,並將response header中的set-cookie進行保存。或者cookie通過報文返回,進而使用腳本進行緩存
- 用戶關閉當前網站窗口後再次打開時,瀏覽器會自動將cookie加入request header實現免登
受攻擊場景:
bank.com網站是一家銀行,在用戶登錄以後,bank.com網站在用戶的當前終端上設置了一個Cookie,這其中包含了一些隱私信息(比如存款金額)。
如果這個時候,七大姑在社交app上給你發了一篇養生文章鏈接,其實這個網頁是個diaoyu網站evil.com,訪問鏈接後就把你重定向到一個嵌入了 iframe 的攻擊網站。
而這個時候如果沒有跨域限制,這個iframe會自動加載銀行網站的留存信息,讀取到bank.com網站的Cookie,那麼用戶的信息就會泄露,更可怕的是,Cookie往往是用來保存用戶的登錄狀態,如果用戶沒有退出登錄,其他的網站就可以冒充用戶,爲所欲爲,控制 iframe 的 DOM,通過一系列騷操作把你卡里的錢轉走。
沒有同源策略:
有同源策略:
而同源策略,也就是跨域限制的出現,限制了cookie的命名區域,使攻擊者無法直接獲取cookie的內容本身。
下面就讓我們一起來了解一下什麼是同源策略。
3 url的組成
在瞭解同源策略之前,我們需要先對一個url的各個組成部分進行初步瞭解:
- 協議部分:該URL的協議部分爲“http:”,這代表網頁使用的是什麼通信協議。在Internet中可以使用多種協議,如HTTP、HTTPS、FTP等等。本例中使用的是HTTP協議。在"HTTP"後面的“//”爲分隔符。
- 域名部分:該URL的域名部分爲“www.a.com”。一個URL中,也可以使用IP地址作爲域名使用。
- 端口部分:跟在域名後面的是端口,域名和端口之間使用“:”作爲分隔符。端口不是一個URL必須的部分,如果省略端口部分,將採用默認端口,http爲80,https爲443,FTP爲21。
- 虛擬目錄部分:從域名後的第一個“/”開始到最後一個“/”爲止,是虛擬目錄部分。虛擬目錄也不是一個URL必須的部分。本例中的虛擬目錄是“/news/”。
- 文件名部分:從域名後的最後一個“/”開始到“?”爲止,是文件名部分。如果沒有“?”,則是從域名後的最後一個“/”開始到“#”爲止,是文件部分。如果沒有“?”和“#”,那麼從域名後的最後一個“/”開始到結束,都是文件名部分。本例中的文件名是“index.html”。文件名部分也不是一個URL必須的部分。
- 參數部分:從“?”開始到“#”爲止之間的部分爲參數部分,又稱搜索部分、查詢字符串。本例中的參數部分爲“boardID=5&ID=24618&page=1”。查詢字符串中允許有多個參數,參數與參數之間用“&”作爲分隔符。
- 錨部分:從“#”開始到最後,都是錨部分。本例中的錨部分是“name”。錨部分也不是一個URL必須的部分。
以上,我們已經大致瞭解了一個url的基本組成。
4 同源策略(SOP - same origin policy)
它是由 Netscape(美國網景公司) 提出的一個重要的安全策略,現在所有支持 JavaScript 的瀏覽器都會使用這個策略。
4.1 作用
同源策略作爲瀏覽器安全的基石,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響,如個人信息將不再具有安全性。可以說 Web 是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現。
它的核心就在於它認爲自任何站點加載的內容都是不安全的。當被瀏覽器半信半疑的腳本運行在沙箱時,它們應該只被允許訪問來自同一站點的資源,而不是那些來自其它站點可能懷有惡意的資源。
因此,出於安全原因,對於跨源HTTP請求,瀏覽器禁止發起請求,或者允許發起請求,服務端也能收到請求並正常返回結果,但是瀏覽器會對返回結果進行攔截。 例如,XMLHttpRequest 和Fetch API 遵循同源策略,這意味着使用這些API的Web應用程序只能從加載應用程序的同一個域請求HTTP資源,除非服務器同意訪問。譬如服務器對預檢請求的響應 Header 中有 Access-Control-Allow-Origin: *,那麼跨域請求即可正確訪問。
簡單來說,同源策略就是瀏覽器的一個安全限制,它阻止了不同【域】之間進行的數據交互。
那麼是如何定義一個請求是否滿足同源要求的呢?
4.2 同源的判斷標準
- 協議相同
- 域名相同
- 端口相同
4.3 跨域示例
URL | 說明 | 是否允許通信 |
---|---|---|
http://www.a.com/a.jshttp://www.a.com/b.js | 同一域名,不同路徑 | 允許 |
http://www.a.com:8080/a.jshttp://www.a.com/a.js | 同一域名,不同端口 | 不允許 |
http://www.a.com/a.jshttps://www.a.com/a.js | 同一域名,不同協議 | 不允許 |
http://www.a.comhttp://www.b.com | 域名不同 | 不允許 |
http://www.a.com/a.jshttp://script.a.com/a.js | 主域相同,子域不同 | 不允許 |
4.4 同源策略的限制內容
- 禁止跨域操作DOM,也就是無法接觸非同源網頁的 DOM。
- 禁止跨域資源請求,也就是無法向非同源地址發送 AJAX 請求(可以發送,但瀏覽器會拒絕接受響應)。
- 禁止跨域讀取 Cookie、LocalStorage,也就是無法讀取非同源網頁的 Cookie、LocalStorage。
4.5 允許跨域的情況
另外,我們知道通過 JavaScript 腳本可以拿到其他窗口的window對象。如果是非同源的網頁,目前允許一個窗口可以接觸其他網頁的window對象的九個屬性和四個方法。
- window.closed - 只讀,判斷當前窗口是否關閉
- window.frames - 只讀,獲取窗口中所有命名的框架
- window.length - 只讀,獲取當前窗口中frames的數量(包括iframes)
- window.location - 可讀寫。非同源的情況下,只允許調用location.replace()方法和寫入location.href屬性
- window.opener - 只讀,獲取對創建該窗口的window對象的引用
- window.parent - 只讀,父窗口
- window.self - 只讀,對自己的引用,window.window == window.self
- window.top - 只讀,獲取最頂層窗口對象的引用
- window.window - 只讀,對自己的引用,window.window == window
- window.blur() - 失焦
- window.close() - 關閉當前窗口
- window.focus() - 聚焦當前窗口
- window.postMessage() - 跨域通信API
4.6 允許跨域加載資源的標籤
<img src=XXX>
<link href=XXX>
<script src=XXX>
<iframe src=XXX>
ps: 超鏈接<a>標籤、<form>標籤中的action行爲也可以進行跨域
5 跨域解決方案一覽圖
6 後續
綜上,我們完成了對跨域的初識,後面我們將對跨域的解決方案進行探討,從上述的九種跨域解決方案進行一一描述,敬請期待。
作者:京東物流 李菲菲
來源:京東雲開發者社區 自猿其說Tech 轉載請註明來源