跨域對于前端來講是個耳熟的字眼,在日常工作和面試中經常出現。
本文介紹了同源策略
、CORS跨域資源共享
、實際開發中如何處理跨域
這幾個方面。
前端開發過程中,你肯定見過這個報錯。Access to fetch at 'https://m.demo.com' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
這就是跨域請求被瀏覽器攔截沒跑了。
same origin policy & CORS
同源策略 (same origin policy)
,是瀏覽器最核心、最基本的安全功能之一。它限制了從一個源(origin)
加載的文檔如何與另外一個源的資源進行交互。
Web應用程序只能從加載應用程序的同一個域請求HTTP資源,除非響應報文包含了正確CORS響應頭。
更詳細的說,是瀏覽器對在腳本內跨源發起的 http請求 的response結果進行了攔截。
想象一下,如果沒有這個策略限制,就會發生某一個源隨意調用其他源的接口獲取他人數據,篡改他們數據……等等肥腸價值觀警告的事情。
該策略常見的三種情況:
- Cross-origin writes 跨域寫 例如鏈接links、重定向、表單提交……
- Cross-origin embedding 資源嵌入 比如 script 、link、img、video等標簽嵌入資源
- Cross-origin reads 跨域讀 但可通過嵌入巧妙繞過
同源
就是指兩個頁面的協議、主機、端口號(如果有)三者皆一致。 當一個資源a從一個與該資源a所在服務器a不同協議、域或端口 的服務器b上請求另一個資源b,這就發起了一個跨域HTTP請求。
exp:http:/ / www. baidu. com:80 http即協議,www. baidu . com即主機,80即端口號。
但有時候開發中,確實存在這樣、那樣的跨域資源獲取需求。這就要講到跨域資源共享(Cross-Origin Resource Sharing)
,允許服務器聲明 哪些源 有權限通過瀏覽器 訪問哪些資源。給我們爭取一個跨域請求的機會。
借用個栗子,客戶端發起一個simple request get請求:
-
Client
request header
請求頭 get 請求獲取 /resources/public-data/ 下的資源
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/\*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http:\/\/foo.example\/examples\/access-control\/simpleXSInvocation.html
Origin: http:\/\/foo.example
-
Server
response header
響應頭
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[XML Data]
服務端返回的 Access-Control-Allow-Origin: *
表明,該資源可被任意外域訪問。
ps:如果服務器僅希望某個源(比如http:/ /examples.com)可以訪問其資源,只需設置 Access-Control-Allow-Origin: http:/ /examples.com 。如果希望僅允許GET請求或多種請求,則可設置 Access-Control-Allow-Methods:GET, POST, ……
。
解決方法
已知的一些方法在生產環境幾乎都需要server
進行配合,本地開發的話前端是完全可以自行繞過跨源問題。
方法如下(前四種適用于生產環境):
1、后端在reponse header中添加 Access-Control-Allow-Origin: xxx
允許跨源請求。
exp,http: //hello-world.example 想訪問 http: //example.com 的資源。 http: //example.com 在response header 中添加
Access-Control-Allow-Origin: http: //hello-world.example
。
當然 也可以一了百了添加Access-Control-Allow-Origin: *
,只要你確保不會產生其他副作用。
2、前端使用JSONP。
像一些資源標簽(如img,video,script……)允許跨源資源嵌入,即標簽的src是允許嵌入跨源資源的。
jsonp
其實就是通過 script src 傳遞參數(請求參數、約定的callback name)給服務器,服務器將返回數據包裹在回調函數中,直接在腳本中進行調用達到跨域獲取資源數據的效果。
3、document.domain,允許子域安全訪問其父域。
document.domain
的值可以設置為其當前域或其當前域的父域。如果將其設置為其當前域的父域,則這個較短的父域將用于后續源檢查。(設置后當前域端口號會被重寫為Null)。
舉個栗子,假設http: //demo.example.com中有一個腳本執行了 document.domain = 'example.com'; 那么瀏覽器將會通過對http: //company.com/dir/page.html (http: //company.com/dir/page.html 也必須設置document.domain = 'example.com'; 以重置端口號)資源的同源檢測。
4、OPTIONS預檢請求
preflight request 是一個包含了:
Access-Control-Request-Method
和Access-Control-Request-Headers
,以及一個Origin
首部的options請求。服務器基于從預檢請求獲得的信息來判斷,是否接受接下來的實際請求。
對于可能對服務器數據產生副作用的請求(GET以外,搭配某些MIME type的請求),瀏覽器都會先發出一個預檢請求,以監測服務器是否允許該類型的請求。確認允許后,才會發出實際請求。
OPTIONS 請求用于獲取目的資源所支持的通信選項。
5、如果是本地開發,還可使用chrome - Allow CORS: Access-Control-Allow-origin插件允許跨域,或命令行 open -a "Google Chrome" --args --disable-web-security --user-data-dir
。
chrome插件的做法應該就是在 response 達到瀏覽器前,在 response header 中添加了 Access-Control-Allow-Origin 和 Access-Control-Allow-Methods 兩個字段來達到允許跨域資源共享的原理。
6、webpack proxy
可以在開發環境代理跨域請求,但build打包后,代理是不生效的,請注意這點。
參考資料
CORS:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
瀏覽器同源策略:https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy
Document.domain:https://developer.mozilla.org/zh-CN/docs/Web/API/Document/domain
W3C Cross-Origin Resource Sharing:https://www.w3.org/TR/cors/
如有遺漏的方法 歡迎補充討論 ~