CORS過程的介紹以及跨域問題
參考文章:https://mp.weixin.qq.com/s/Re1fvKKzi-rPpu6SmpqTJA
文中的動(dòng)圖以及部分總結(jié)語句均引用與參考文章。我這里只是為了讓自己理解深刻一點(diǎn)而嘗試用自己的語言去總結(jié)一遍。推薦閱讀原文
說到跨域問題,我們最常見的就是在我們向服務(wù)器發(fā)起請(qǐng)求的時(shí)候會(huì)遇到。比如說如下情況
假設(shè)我們正在訪問 https://api.mysebsite.com
的站點(diǎn),當(dāng)我們點(diǎn)擊按鈕,向 https://api.mysebsite/users.com
發(fā)送請(qǐng)求,獲取網(wǎng)站上的一些用戶信息。這個(gè)時(shí)候從結(jié)果上是完美的。原因是,根據(jù)同源策略,我們發(fā)起請(qǐng)求的域名,端口,協(xié)議均是一至的。瀏覽器并不會(huì)產(chǎn)生跨域報(bào)錯(cuò)。
而如果,以上提到的三點(diǎn)其中一點(diǎn)不滿足的站點(diǎn),再向服務(wù)器發(fā)起請(qǐng)求,那么這個(gè)時(shí)候就會(huì)觸發(fā)跨域報(bào)錯(cuò)。如下圖
而以上兩種情況出現(xiàn)的原因,其實(shí)就是我們今天要介紹的內(nèi)容。
首先我們先來介紹一下同源策略
同源策略
瀏覽器網(wǎng)絡(luò)請(qǐng)求的時(shí)候,有一個(gè)同源策略的機(jī)制,即默認(rèn)情況下,使用API的Web應(yīng)用程序只能從加載應(yīng)用程序的同一個(gè)域請(qǐng)求HTTP資源。也就是我們上面提到的,必須要求域名,端口,協(xié)議都要一至。請(qǐng)求才能發(fā)送成功。而只要其中一點(diǎn)不一致,那么該請(qǐng)求也算是跨域的。
所以我們同樣可以看得出來。同源策略會(huì)限制以下三種行為:
- Cookie,LocalStorage,和IndexDB 訪問受限
- 無法操作跨域DOM(常見的如 iframe)
- js發(fā)起的XHR和Fetch請(qǐng)求受限
作用
說了這么多其實(shí),我們可以看出同源策略的作用其實(shí)就是為了保證用戶的信息安全。打個(gè)比方,如果沒有同源策略,那么當(dāng)你在不小心的情況下,點(diǎn)擊了網(wǎng)頁的釣魚網(wǎng)站。然后惡意的網(wǎng)站很容易就能利用重定向把你帶到一個(gè)iframe 的 攻擊網(wǎng)站,這個(gè)iframe會(huì)自動(dòng)加載銀行網(wǎng)站,并通過Cookie登錄你的賬號(hào)。然后操作你的DOM,進(jìn)行一系列的危險(xiǎn)操作。
而我們當(dāng)然不希望這種情況發(fā)生,這個(gè)時(shí)候同源策略就起到有效的保護(hù)作用。因?yàn)樗_保了我們只能訪問同一站點(diǎn)的資源
好了,接下來要說的就是CORS了。
瀏覽器的CORS
瀏覽器出于安全的考慮,會(huì)限制從腳本內(nèi)發(fā)起的跨域HTTP請(qǐng)求。(注意是腳本內(nèi))例如XHR 和 Fetch 就遵循同源策略。這意味著使用 API 的Web應(yīng)用程序只能從加載應(yīng)用程序的同一個(gè)域請(qǐng)求HTTP資源。
在日常的開發(fā)中,我們很多時(shí)候都會(huì)跨域去請(qǐng)求別的站點(diǎn)的資源。而這個(gè)時(shí)候我們?yōu)榱私鉀Q跨域的問題就要利用CORS機(jī)制。
CORS(Cross-Origin Resource Sharing),即跨域資源共享。如字面意思,CORS機(jī)制的存在就是為了讓我們?cè)诒WC安全的前提下,實(shí)現(xiàn)訪問不同域下的資源,這算是放寬的政策。
瀏覽器可以利用CORS機(jī)制,放行一些符合規(guī)范的跨域訪問,阻止不符合規(guī)范的跨域訪問。下面我我們就來介紹一下瀏覽器內(nèi)部是如何實(shí)現(xiàn)的。
Web程序發(fā)出跨域請(qǐng)求后,瀏覽器會(huì)自動(dòng)向我們的HTTP header添加一個(gè)額外的字段 Origin
。Origin
標(biāo)記了請(qǐng)求的站點(diǎn)來源:
GET https://api.website.com/users HTTP/1/1
Origin: https://www.mywebsite.com -> 瀏覽器自己攜帶的
為了使瀏覽器允許訪問跨域資源,服務(wù)器返回的 response 還需要加一些響應(yīng)頭字段,這些字段可以顯式的表明此服務(wù)器是否允許跨域請(qǐng)求。
服務(wù)端的CORS
作為服務(wù)端人員,我們?yōu)榱嗽试S符合規(guī)則的跨域請(qǐng)求。我們可以通過在HTTP的響應(yīng)中添加響應(yīng)字段 Access-Control-*
來表明是否允許跨域請(qǐng)求。根據(jù)這些CORS響應(yīng)頭字段,瀏覽器可以允許一些被同源策略限制的跨域響應(yīng)
雖然有好幾個(gè)CORS的字段,但是有一個(gè)必須要加的字段是Acess-Control-Allow
。這個(gè)頭字段的值指定了哪些站點(diǎn)被允許跨域訪問資源的。
- 如果我們有服務(wù)器的開發(fā)權(quán)限,我們可以給
https://www.mywebsite.com
加上訪問權(quán)限,只需要把這個(gè)域名添加到Access-Control-Allow-Origin
中
現(xiàn)在這個(gè)字段會(huì)被添加到服務(wù)端的響應(yīng)報(bào)文中,然后返回給客戶端。然后這個(gè)時(shí)候客戶端再向服務(wù)端發(fā)起跨域請(qǐng)求,同源策略將不會(huì)再限制 https://api.mywebsite.com
站點(diǎn)返回的資源。
報(bào)文如下:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.mywebsite.com
Date: Fri, 11 Oct 2019 15:47 GM
Content-Length: 29
Content-Type: application/json
Server: Apache
{user: [{...}]}
- 收到服務(wù)器返回的response后,瀏覽器中的CORS機(jī)制會(huì)檢查
Access-Control-Allow-Origin
的值是否等于 request 中 Origin的值
- 瀏覽器通過校驗(yàn)之后,前端成功接收到跨域站點(diǎn)的資源
而相反,如果對(duì)比Access-Control-Allow-Origin
和 Origin
的值不一致的時(shí)候,瀏覽器會(huì)拋出一個(gè) CORS Error的報(bào)錯(cuò)信息。
當(dāng)然,我們還可以通過配置 * 作為
Access-Control-Allow-Origin
的值,這樣的話任意的外域訪問都會(huì)被允許。但是這并不是解決允許多域名請(qǐng)求同一站點(diǎn)這個(gè)場景的最優(yōu)解下面我會(huì)再單獨(dú)講一講這種場景的解決方案。
除了 Access-Control-Allow-Origin
字段頭之外,開發(fā)人員還可以通過其他字段對(duì)請(qǐng)求作出限制。比如說 Access-Control-Allow-Methods
該字段用來指明跨域請(qǐng)求所允許的 HTTP 方法。
如上圖中,只有請(qǐng)求方法為 GET
,POST
或 PUT
方法被允許跨域訪問資源。其他的HTTP方法,例如 PATCH
和 DELETE
都會(huì)產(chǎn)生預(yù)檢。
這里就引申出一些別的內(nèi)容,比如說預(yù)檢
什么是預(yù)檢呢?
預(yù)檢 = 預(yù)請(qǐng)求(options請(qǐng)求)
- 在發(fā)起實(shí)際請(qǐng)求之前,如果這個(gè)請(qǐng)求是一個(gè)復(fù)雜請(qǐng)求,那么客戶端會(huì)先發(fā)出一個(gè)options請(qǐng)求。預(yù)請(qǐng)求中的
Access-Control-Request-*
包含了我們將要處理的實(shí)際請(qǐng)求信息。
首部字段
Access-Control-Request-Method
會(huì)告知服務(wù)器,實(shí)際請(qǐng)求要用到什么方法
首部字段Access-Control-Request-Headers
會(huì)告知服務(wù)器,實(shí)際請(qǐng)求將附帶的自定義請(qǐng)求首部字段是什么
OPTIONS https://api.mywebsite.com/user/1 HTTP/1.1
Origin: https://www.mywebsite.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type
- 服務(wù)器接收到預(yù)請(qǐng)求后,會(huì)返回一個(gè)沒有 body的HTTP響應(yīng),這個(gè)響應(yīng)標(biāo)記了服務(wù)器允許的 HTTP 方法和 Http Header字段
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://www.mywebsite.com
Access-Control-Request-Method: GET POST PUT
Access-Control-Request-Headers: Content-Type
- 瀏覽器收到預(yù)請(qǐng)求的響應(yīng),并檢查是否應(yīng)允許發(fā)送實(shí)際請(qǐng)求
上圖漏了
Access-Control-Allow-Headers: Content-Type
- 如果預(yù)請(qǐng)求通過了,瀏覽器會(huì)將實(shí)際請(qǐng)求發(fā)送到服務(wù)器,然后服務(wù)器會(huì)返回我們想要的資源
而此處有一個(gè)優(yōu)化點(diǎn),既然每一次復(fù)雜請(qǐng)求都會(huì)重復(fù) options請(qǐng)求,那么我們可以通過設(shè)置一個(gè)字段
Access-Control-Max-Age
來緩存預(yù)請(qǐng)求的響應(yīng)。瀏覽器可以使用緩存來代替發(fā)送新的預(yù)請(qǐng)求。
到這里,CORS和跨域的關(guān)系基本就說清楚了。下面我們?cè)僬f一點(diǎn)補(bǔ)充
- 認(rèn)證
XHR 或 Fetch 與 CORS的一個(gè)有趣的特性是,我們可以基于 Cookies 和 HTTP 認(rèn)證信息發(fā)送身份憑證。一般而言,對(duì)于跨域 XHR 或 Fetch 請(qǐng)求,瀏覽器不會(huì)發(fā)送身份憑證信息。
盡管 CORS 默認(rèn)情況下不發(fā)送身份憑證,但我們?nèi)匀豢梢酝ㄟ^添加Access-Control-Allow-Credentials
CORS響應(yīng)頭來更改它
如果要在跨域請(qǐng)求中包含 cookie 和其他授權(quán)信息,我們需要做一下操作:
- XHR 請(qǐng)求中將
withCredentials
字段設(shè)置為 true - Fetch 請(qǐng)求中將
credentials
字段設(shè)置為 include - 服務(wù)器把
Access-Control-Allow-Credentials: true
添加到響應(yīng)頭中
// 瀏覽器 fetch 請(qǐng)求
fetch('https://api.mywebsite,com.users', {
credentials: "include"
})
// 瀏覽器 XHR 請(qǐng)求
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;
// 服務(wù)器添加認(rèn)證字段
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
做到了上面幾點(diǎn)之后,就能在跨域請(qǐng)求中攜帶身份憑證了。
補(bǔ)充
- 我們上面提到可以通過設(shè)置
Access-Control-Allow-Origin
去允許跨域請(qǐng)求。但是如果想要允許多個(gè)域名訪問同一個(gè)站點(diǎn)的資源的時(shí)候。我們顯然是不能寫死的。這個(gè)時(shí)候有就以下幾個(gè)解決方案:
a. Access-Control-Allow-Origin 設(shè)置為 * (不推薦)
b. 通過nginx代理服務(wù)器進(jìn)行設(shè)置
c. 修改 api 接口,在每個(gè)api上添加響應(yīng)頭 (待考究)
d. 攔截器方式 (微軟提供的最佳方案,實(shí)際上是對(duì)c方案的封裝)d的方案就是寫一個(gè)攔截器,應(yīng)用到所有控制器上,在攔截器里控制來訪域名,動(dòng)態(tài)設(shè)置Access-Control-Allow-Origin的值.
簡單請(qǐng)求不會(huì)觸發(fā)預(yù)請(qǐng)求,但是會(huì)根據(jù)請(qǐng)求響應(yīng)的報(bào)文去判斷是否跨域了(即對(duì)比origin,和Access-Control-Allow-Origin的值是否一致)
簡單請(qǐng)求這種,實(shí)際是已經(jīng)發(fā)去了的請(qǐng)求,也就是說請(qǐng)求已經(jīng)到了服務(wù)器了,然后返回來了。只是瀏覽器攔截了響應(yīng),而并不是攔截了請(qǐng)求本身
以上就是全部內(nèi)容了