前端面試必會網絡跨域問題解決方法
什麼是跨域
瀏覽器有一個重要的安全策略,稱之為「同源策略」
其中,源=協議+主機+端口源=協議+主機+端口源=協議+主機+端口,兩個源相同,稱之為同源,兩個源不同,稱之為跨源或跨域
比如:
源 1 | 源 2 | 是否同源 |
---|---|---|
www.baidu.com | www.baidu.com/news | ✅ |
www.baidu.com | www.baidu.com | ❌ |
http://localhost:5000 | http://localhost:7000 | ❌ |
http://localhost:5000 | http://127.0.0.1:5000 | ❌ |
www.baidu.com | baidu.com | ❌ |
同源策略是指,若頁面的源和頁面運行過程中加載的源不一致時,出於安全考慮,瀏覽器會對跨域的資源訪問進行一些限制
同源策略對 ajax 的跨域限制的最為兇狠,默認情況下,它不允許 ajax 訪問跨域資源
所以,我們通常所說的跨域問題,就是同源策略對 ajax 產生的影響
有多種方式解決跨域問題,常見的有:
- 代理,常用
- CORS,常用
- JSONP
無論使用哪一種方式,都是要讓瀏覽器知道,我這次跨域請求的是自己人,就不要攔截瞭。
跨域解決方法1-代理
對於前端開發而言,大部分的跨域問題,都是通過代理解決的
代理適用的場景是:生產環境不發生跨域,但開發環境發生跨域
因此,隻需要在開發環境使用代理解決跨域即可,這種代理又稱之為開發代理
在實際開發中,隻需要對開發服務器稍加配置即可完成
// vue 的開發服務器代理配置 // vue.config.js module.exports = { devServer: { // 配置開發服務器 proxy: { // 配置代理 "/api": { // 若請求路徑以 /api 開頭 target: "http://dev.taobao.com", // 將其轉發到 http://dev.taobao.com }, }, }, };
跨域解決方法2-JSONP
在CORS出現之前,人們想瞭一種奇妙的辦法來實現跨域,這就是JSONP。
要實現JSONP,需要瀏覽器和服務器來一個天衣無縫的絕妙配合。
JSONP的做法是:
當需要跨域請求時,不使用AJAX,轉而生成一個script元素去請求服務器,由於瀏覽器並不阻止script元素的請求,這樣請求可以到達服務器。服務器拿到請求後,響應一段JS代碼,這段代碼實際上是一個函數調用,調用的是客戶端預先生成好的函數,並把瀏覽器需要的數據作為參數傳遞到函數中,從而間接的把數據傳遞給客戶端
JSONP有著明顯的缺點,即其隻能支持GET請求
跨域解決方法3-CORS
概述
CORS
是基於http1.1
的一種跨域解決方案,它的全稱是Cross-Origin Resource Sharing,跨域資源共享。
它的總體思路是:如果瀏覽器要跨域訪問服務器的資源,需要獲得服務器的允許
而要知道,一個請求可以附帶很多信息,從而會對服務器造成不同程度的影響
比如有的請求隻是獲取一些新聞,有的請求會改動服務器的數據
針對不同的請求,CORS 規定瞭三種不同的交互模式,分別是:
- 簡單請求
- 需要預檢的請求
- 附帶身份憑證的請求
這三種模式從上到下層層遞進,請求可以做的事越來越多,要求也越來越嚴格。
下面分別說明三種請求模式的具體規范。
簡單請求
當瀏覽器端運行瞭一段 ajax 代碼(無論是使用 XMLHttpRequest 還是 fetch api),瀏覽器會首先判斷它屬於哪一種請求模式
簡單請求的判定
當請求同時滿足以下條件時,瀏覽器會認為它是一個簡單請求:
請求方法屬於下面的一種:
- get
- post
- head
請求頭僅包含安全的字段,常見的安全字段如下:
Accept
Accept-Language
Content-Language
Content-Type
DPR
Downlink
Save-Data
Viewport-Width
Width
請求頭如果包含Content-Type
,僅限下面的值之一:
text/plain
multipart/form-data
application/x-www-form-urlencoded
如果以上三個條件同時滿足,瀏覽器判定為簡單請求。
下面是一些例子:
// 簡單請求 fetch('http://crossdomain.com/api/news'); // 請求方法不滿足要求,不是簡單請求 fetch('http://crossdomain.com/api/news', { method: 'PUT', }); // 加入瞭額外的請求頭,不是簡單請求 fetch('http://crossdomain.com/api/news', { headers: { a: 1, }, }); // 簡單請求 fetch('http://crossdomain.com/api/news', { method: 'post', }); // content-type不滿足要求,不是簡單請求 fetch('http://crossdomain.com/api/news', { method: 'post', headers: { 'content-type': 'application/json', }, });
簡單請求的交互規范
當瀏覽器判定某個ajax 跨域請求是簡單請求時,會發生以下的事情
- 請求頭中會自動添加
Origin
字段
比如,在頁面http://my.com/index.html
中有以下代碼造成瞭跨域
// 簡單請求 fetch('http://crossdomain.com/api/news');
請求發出後,請求頭會是下面的格式:
GET /api/news/ HTTP/1.1 Host: crossdomain.com Connection: keep-alive ... Referer: http://my.com/index.html Origin: http://my.com
看到最後一行沒,Origin
字段會告訴服務器,是哪個源地址在跨域請求
- 服務器響應頭中應包含
Access-Control-Allow-Origin
當服務器收到請求後,如果允許該請求跨域訪問,需要在響應頭中添加Access-Control-Allow-Origin
字段
該字段的值可以是:
- *:表示我很開放,什麼人我都允許訪問
- 具體的源:比如
http://my.com
,表示我就允許你訪問
實際上,這兩個值對於客戶端http://my.com
而言,都一樣,因為客戶端才不會管其他源服務器允不允許,就關心自己是否被允許
當然,服務器也可以維護一個可被允許的源列表,如果請求的Origin
命中該列表,才響應*
或具體的源
為瞭避免後續的麻煩,強烈推薦響應具體的源
假設服務器做出瞭以下的響應:
HTTP/1.1 200 OK Date: Tue, 21 Apr 2020 08:03:35 GMT ... Access-Control-Allow-Origin: http://my.com ... 消息體中的數據
當瀏覽器看到服務器允許自己訪問後,高興的像一個兩百斤的孩子,於是,它就把響應順利的交給 js,以完成後續的操作
下圖簡述瞭整個交互過程
需要預檢的請求
簡單的請求對服務器的威脅不大,所以允許使用上述的簡單交互即可完成。
但是,如果瀏覽器不認為這是一種簡單請求,就會按照下面的流程進行:
- 瀏覽器發送預檢請求,詢問服務器是否允許
- 服務器允許
- 瀏覽器發送真實請求
- 服務器完成真實的響應
比如,在頁面http://my.com/index.html
中有以下代碼造成瞭跨域
// 需要預檢的請求 fetch('http://crossdomain.com/api/user', { method: 'POST', // post 請求 headers: { // 設置請求頭 a: 1, b: 2, 'content-type': 'application/json', }, body: JSON.stringify({ name: '袁小進', age: 18 }), // 設置請求體 });
瀏覽器發現它不是一個簡單請求,則會按照下面的流程與服務器交互
- 瀏覽器發送預檢請求,詢問服務器是否允許
OPTIONS /api/user HTTP/1.1 Host: crossdomain.com ... Origin: http://my.com Access-Control-Request-Method: POST Access-Control-Request-Headers: a, b, content-type
可以看出,這並非我們想要發出的真實請求,請求中不包含我們的請求頭,也沒有消息體。
這是一個預檢請求,它的目的是詢問服務器,是否允許後續的真實請求。
預檢請求沒有請求體,它包含瞭後續真實請求要做的事情
預檢請求有以下特征:
請求方法為OPTIONS
沒有請求體
請求頭中包含
Origin
:請求的源,和簡單請求的含義一致Access-Control-Request-Method
:後續的真實請求將使用的請求方法Access-Control-Request-Headers
:後續的真實請求會改動的請求頭
- 服務器允許
服務器收到預檢請求後,可以檢查預檢請求中包含的信息,如果允許這樣的請求,需要響應下面的消息格式
HTTP/1.1 200 OK Date: Tue, 21 Apr 2020 08:03:35 GMT ... Access-Control-Allow-Origin: http://my.com Access-Control-Allow-Methods: POST Access-Control-Allow-Headers: a, b, content-type Access-Control-Max-Age: 86400 ...
對於預檢請求,不需要響應任何的消息體,隻需要在響應頭中添加:
Access-Control-Allow-Origin
:和簡單請求一樣,表示允許的源
Access-Control-Allow-Methods
:表示允許的後續真實的請求方法
Access-Control-Allow-Headers
:表示允許改動的請求頭
Access-Control-Max-Age
:告訴瀏覽器,多少秒內,對於同樣的請求源、方法、頭,都不需要再發送預檢請求瞭
- 瀏覽器發送真實請求
預檢被服務器允許後,瀏覽器就會發送真實請求瞭,上面的代碼會發生下面的請求數據
POST /api/user HTTP/1.1 Host: crossdomain.com Connection: keep-alive ... Referer: http://my.com/index.html Origin: http://my.com {"name": "xiaoming", "age": 18 }
- 服務器響應真實請求
HTTP/1.1 200 OK Date: Tue, 21 Apr 2020 08:03:35 GMT ... Access-Control-Allow-Origin: http://my.com ... 添加用戶成功
可以看出,當完成預檢之後,後續的處理與簡單請求相同
下圖簡述瞭整個交互過程
附帶身份憑證的請求
默認情況下,ajax 的跨域請求並不會附帶 cookie,這樣一來,某些需要權限的操作就無法進行
不過可以通過簡單的配置就可以實現附帶 cookie
// xhr var xhr = new XMLHttpRequest(); xhr.withCredentials = true; // fetch api fetch(url, { credentials: 'include', });
這樣一來,該跨域的 ajax 請求就是一個附帶身份憑證的請求
當一個請求需要附帶 cookie 時,無論它是簡單請求,還是預檢請求,都會在請求頭中添加cookie
字段
而服務器響應時,需要明確告知客戶端:服務器允許這樣的憑據
告知的方式也非常的簡單,隻需要在響應頭中添加:Access-Control-Allow-Credentials: true
即可
對於一個附帶身份憑證的請求,若服務器沒有明確告知,瀏覽器仍然視為跨域被拒絕。
另外要特別註意的是:對於附帶身份憑證的請求,服務器不得設置 Access-Control-Allow-Origin 的值為*。這就是為什麼不推薦使用*的原因
一個額外的補充
在跨域訪問時,JS 隻能拿到一些最基本的響應頭,如:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要訪問其他頭,則需要服務器設置本響應頭。
Access-Control-Expose-Headers
頭讓服務器把允許瀏覽器訪問的頭放入白名單,例如:
Access-Control-Expose-Headers: authorization, a, b
這樣 JS 就能夠訪問指定的響應頭瞭。
以上就是前端面試必會網絡跨域問題解決方法的詳細內容,更多關於前端面試網絡跨域的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- go 原生http web 服務跨域restful api的寫法介紹
- VUE跨域詳解以及常用解決跨域的方法
- 解決golang gin框架跨域及註解的問題
- 深入淺析同源與跨域,jsonp(函數封裝),CORS原理
- Nginx 代理解決跨域問題多種情況分析