跨域問題一直是面試中的經典問題,不管是前端老鳥還是新鳥都碰到過。其中針對跨源Ajax請求中有一個終極解決辦法——CORS(跨源資源共享)大家肯定也不陌生,一說這個名詞,我們就會嘩啦嘩啦說出來一套又一套的理論知識,但是這些理論知識很多我們做的僅僅是去背誦,很少去驗證每一個理論點,本節我們將通過實驗的方式去驗證這些理論點,通過理論與實踐相結合的方式徹底理解CORS。
一、理論知識
既然是CORS,背背這些理論點肯定不為過吧,我就用三幅圖對這個理論進行一些簡單的總結
1.1 請求類型
1.1.1 簡單請求
1.1.2 非簡單請求
1.2 請求如何帶上Cookie信息
二、實驗
為實驗做好前期準備工作,包含一個html頁面和一個服務器程序,其中html訪問網址為http://127.0.0.1:8009; 服務器監聽端口為:8010.
- html頁面初始代碼
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test CORS</title>
</head>
<body>
CORS
<script src="https://code.bdstatic.com/npm/axIOS@0.20.0/dist/axios.min.js"></script>
<script>
axios('http://127.0.0.1:8010', {
method: 'get'
}).then(console.log)
</script>
</body>
</html>
- 服務器端代碼(用express框架)
const express = require('express');
const App = express();
app.get('/', (req, res) => {
console.log('get請求收到了?。。?#39;);
res.send('get請求已經被處理');
})
app.listen(8010, () => {
console.log('8010 is listening')
});
2.1 實驗一
實驗目的:
非同源會產生跨域問題
跨域是瀏覽器對響應攔截造成的
- 首先來看看瀏覽器控制臺內容
控制臺內容顯示報錯,報錯內容是跨域,這是因為端口不同(一個8009一個8010),兩者不同源,所以導致跨域,驗證了實驗目的1.
- 緊接著來瞅瞅服務器控制臺打印了啥內容
控制臺內容打印了接收到了get請求,則證明瀏覽器的請求發出了并被服務器端正常接收,從側面證明了跨域是瀏覽器對響應進行了攔截,從而驗證了實驗目的2.
2.2 實驗二
實驗目的
服務器配置
Access-Control-Allow-Origin會解決跨域問題瀏覽器通過響應頭中是否包含
Access-Control-Allow-Origin這個響應頭的值與請求頭中Origin是否相等來確定是否能夠進行跨域訪問
- 首先來摟一眼請求頭
- 緊接著再來摟一眼響應頭
按照理論來說,請求頭中的Origin字段表示本次請求來自哪個源(協議+域名+端口),服務器根據這個值來決定是否同意這次請求。如果Origin指定的源不在許可范圍內,服務器會返回一個正常的HTTP回應。目前并沒有修改,還處于報錯狀態,該響應確實是一個正常響應,不包含
Access-Control-Allow-Origin字段,但是這也不足以說服我瀏覽器是通過驗證該字段來確實是否允許跨域請求。所以緊接著需要做一個對比試驗,通過修改服務端的代碼后觀察響應頭內容。
- 從最簡單的修改開始,直接將響應頭中加入Access-Control-Allow-Origin=“http://127.0.0.1:8009” 字段,理論上來說此時會允許所有的跨域請求。
服務端代碼修改后內容
app.get('/', (req, res) => {
console.log('get請求收到了?。。?#39;);
res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:8009');
res.send('get請求已經被處理');
})
響應頭內容
觀察到響應頭中多了一行內容:
Access-Control-Allow-Origin:http://127.0.0.1:8009 字段,再看看響應內容,確實有消息返回了,內容如下:
- 只驗證了Origin和Access-Control-Allow-Origin中內容響應,若內容不同又會有什么現象呢?
服務端代碼進一步修改
app.get('/', (req, res) => {
console.log('get請求收到了?。?!');
res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:8008');
res.send('get請求已經被處理');
})
響應頭內容
此時瀏覽器控制臺報錯了,出現了跨域問題
通過該實驗可以驗證通過配置
Access-Control-Allow-Origin字段可以解決跨域問題;此外,瀏覽器是通過檢查響應頭中Access-Control-Allow-Origin字段的值與Origin的值是否相等來確定是否允許跨域訪問的。通過該實驗達到了我們實驗的目的。
2.3 實驗三
實驗目的 驗證CORS請求默認不發送Cookie信息,如果要把Cookie發送到服務器,一方面要服務器同意(通過指定
Access-Control-Allow-Origin字段且Access-Control-Allow-Origin需要指定具體域名);另一方面瀏覽器請求中必須帶上withCredentials字段。
- 通過觀察請求頭(看實驗一中請求頭),并不包含Cookie信息
- 代碼修改
index.html頁面進行修改
axios('http://127.0.0.1:8010', {
method: 'get',
withCredentials: true
}).then(console.log)
服務器端代碼修改
app.get('/', (req, res) => {
console.log('get請求收到了?。?!');
console.log('cookie 內容為', req.headers.cookie);
res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:8009');
res.setHeader('Access-Control-Allow-Credentials', true);
res.cookie('test', 'test', {expires: new Date(Date.now() + 900000)});
res.send('get請求已經被處理');
})
- 再次觀察請求頭內容,帶上了cookie
- 看看服務器端有沒有接收到cookie信息,控制臺信息如下,確實收到了cookie信息
按照上述進行配置發送請求過程中將會帶著cookie信息,上一個配置將會報錯(可以自行驗證)
2.4 實驗四
實驗目的
驗證非簡單請求會增加一次預檢請求
預檢請求是Options請求
請求頭中會攜帶非簡單請求的請求方法(
Access-Control-Request-Methods)和頭信息(Access-Control-Request-Headers),預檢請求的響應頭信息中Access-Control-Allow-Methods和Access-Control-Allow-Headers與上述請求頭中的信息匹配才可以發送正常的CORS請求。
- 第一步肯定是要修改代碼了
index.html代碼
axios('http://127.0.0.1:8010', {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
data: {
name: 'dog'
}
}).then(console.log)
服務器代碼修改如下
app.options('/', (req, res) => {
console.log('options請求收到了?。?!');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
res.setHeader('Access-Control-Max-Age', 10000);
res.send('options請求已經被處理');
});
app.post('/', (req, res) => {
console.log('post請求收到了?。?!');
res.setHeader('Access-Control-Allow-Origin', '*');
res.send('post請求已經被處理');
});
- 修改完了代碼是不是要瞅瞅結果呢?
先看看瀏覽器是不是輸出了內容,確實有內容輸出了
再瞅瞅服務器輸出了些什么內容
可以看到本來打算發一次請求,但實際上發了兩條,第一條是Options請求,第二條請求才是post請求,上述打印內容驗證了實驗目的中的一和二。
- 下面繼續深入思考,來看看預檢請求的請求頭和響應頭
請求頭內容
響應頭內容
上述
Access-Control-Request-Headers與Access-Control-Allow-Headers一樣,而且內容也正常返回了(步驟二中已經進行了展示),但是這不足以證明實驗目的三,下面我們認為增加一條頭信息再來看結果。
- 人為增加一條請求頭信息
index.html頁面修改后如下
axios('http://127.0.0.1:8010', {
method: 'post',
headers: {
'Content-Type': 'application/json',
'Test': 'test'
},
data: {
name: 'dog'
}
}).then(console.log)
此時瀏覽器控制臺報錯了
服務端只接收到了options請求
請求頭信息為
響應頭信息為
通過該實驗證明只有
Access-Control-Request-Headers與Access-Control-Allow-Headers相等的時候,預檢請求才會通過,后續請求才會發出,從而達到了該實驗的實驗目的三。