
前言:
本文翻譯自 Lydia Hallie 小姐姐寫的 ????? CS Visualized: CORS,她用了大量的動圖去解釋 CORS 這個概念,國內還沒有人翻譯本文,所以我在原文的理解上翻譯了本文并修改了一些錯誤,希望能幫到大家,
覺得翻譯的不錯一定要點贊哦,謝謝你,這對我真的很重要! ??
“注:原文的動圖均為 keynote 制作
前端開發中,我們經常要使用其他站點的資料,前端顯示這些資料之前,必須向服務器發出請求以獲取該資料,
假設我們正在訪問 https://api.mywebsite.com 這個站點,點擊按鈕向 https://api.mywebsite.com/users 發送請求,獲取網站上的一些用戶資訊:

“??:這里原作者有個筆誤,把
https://api.mywebsite.com誤寫為https://www.mywebsite.com了,圖中也有這個錯誤,讀者要注意一下不要被誤導
從結果上看表現非常完美,我們向服務器發送請求,服務器回傳了我們需要的 JSON 資料,前端也正常的渲染出了結果,
下面我們換一個網站試試,用 https://www.anotherwebsite.com 這個網站向 https://api.website.com/users 發送請求:

問題來了,我們請求同樣的介面網站,但是這次瀏覽器給我們拋出一個 Error,
剛剛瀏覽器拋出的就是 CORS Error,下面讓我們分析一下為什么會產生這種 Error,以及這個 Error 的確切含義是什么,
1.同源策略
瀏覽器網路請求時,有一個同源策略的機制,即默認情況下,使用 API 的 Web 應用程式只能從加載應用程式的同一個域請求 HTTP 資源,
比如說, https://www.mywebsite.com 請求 https://www.mywebsite.com/page 是完全沒有問題的,但是當資源位于不同協議、子域或埠的站點時,這個請求就是跨域的,

目前來看,同源策略會讓三種行為受限:
Cookie、LocalStorage 和 IndexDB 訪問受限 無法操作跨域 DOM(常見于 iframe) Javascript 發起的 XHR 和 Fetch 請求受限
那么,為什么會存在同源策略呢?
我們做個假設,如果不存在同源策略,你無意中點擊了七大姑在微信上給你發的一篇養生文章鏈接,其實這個網頁是個釣魚網站,訪問鏈接后就把你重定向到一個嵌入了 iframe 的攻擊網站,這個 iframe 會自動加載銀行網站,并通過 cookies 登錄你的賬戶,
登陸成功后,這個釣魚網站還可以控制 iframe 的 DOM,通過一系列騷操作把你卡里的錢轉走,

這是一個非常嚴重的安全漏洞,我們不希望自己在互聯網的內容被隨便訪問,更不要說這種涉及到錢的網站了,
同源策略可以幫助我們解決這個安全問題,這個策略確保我們只能訪問同一站點的資源,

在這種情況下,https://www.evilwebsite.com 嘗試跨站訪問 https://www.bank.com 的資源,同源策略就會阻止這個操作,讓釣魚網站無法訪問銀行網站的資料,
說了這么多,同源策略和 CORS 又有什么關系?
2.瀏覽器 CORS
出于安全原因,瀏覽器限制從腳本內發起的跨域 HTTP 請求, 例如 XHR 和 Fetch 就遵循同源策略,這意味著使用 API 的 Web 應用程式只能從加載應用程式的同一個域請求 HTTP 資源,

日常的業務開發中,我們會經常訪問跨域資源,為了安全的請求跨域資源,瀏覽器使用一種稱為 CORS 的機制,
CORS 的全名是 Cross-Origin Resource Sharing,即跨域資源共享,盡管默認情況下瀏覽器禁止我們訪問跨域資源,但是我們可以利用 CORS 放寬這種限制,在保證安全性的前提下訪問跨域資源,
瀏覽器可以利用 CORS 機制,放行符合規范的跨域訪問,阻止不合規范的跨域訪問,瀏覽器內部是怎么做的呢?我們下面就來分析一下,
Web 程式發出跨域請求后,瀏覽器會自動向我們的 HTTP header 添加一個額外的請求頭欄位:Origin,Origin 標記了請求的站點來源:
GET https://api.website.com/users HTTP/1/1
Origin: https://www.mywebsite.com // <- 瀏覽器自己加的

為了使瀏覽器允許訪問跨域資源, 服務器回傳的 response 還需要加一些回應頭欄位,這些欄位將顯式表明此服務器是否允許這個跨域請求,
3.服務端 CORS
作為服務器開發人員,我們可以通過在 HTTP 回應中添加額外的回應頭欄位 Access-Control-* 來表明是否允許跨域請求,根據這些 CORS 回應頭欄位,瀏覽器可以允許一些被同源策略限制的跨源回應,
雖然有好幾個 CORS 回應頭欄位,但有一個欄位是必加的,那就是 Access-Control-Allow-Origin,這個頭欄位的值指定了哪些站點被允許跨域訪問資源,
1?? 如果我們有服務器的開發權限,我們可以給 https://www.mywebsite.com 加上訪問權限:將該域添加到 Access-Control-Allow-Origin 中,

這個回應頭欄位現在被添加到服務器發回給客戶端的 response header 中,這個欄位添加后,如果我們從 https://www.mywebsite.com 發送跨域請求,同源策略將不再限制 https://api.mywebsite.com 站點回傳的資源,
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: [{...}]}

2?? 收到服務器回傳的 response 后,瀏覽器中的 CORS 機制會檢查 Access-Control-Allow-Origin 的值是否等于 request 中 Origin 的值,
在這個例子中,request 的 Origin 是 https://www.mywebsite.com,這和 response 中 Access-Control-Allow-Origin 的值是一樣的:

3?? 瀏覽器校驗通過,前端成功地接收到跨域資源,
那么,當我們試圖從一個沒有在 Access-Control-Allow-Origin 中列出的網站跨域訪問這些資源會發生什么呢?

如上圖所示,從 https://www.anotherwebsite.com 跨域訪問 https://api.mywebsite.com 資源,瀏覽器拋出一個 CORS Error,經過上面的講解,我們可以讀懂這個報錯資訊了:
The 'Access-Control-Allow-Origin' header has a value
'https://www.mywebsite.com' that is not equal
to the supplied origin.
在這種情況下,Origin 的值是 https://www.anotherwebsite.com,然而,服務器在 Access-Control-Allow-Origin 回應頭欄位中沒有標記這個站點,瀏覽器 CORS 機制就阻止了這個回應,我們無法在我們的代碼中獲取回應資料,
“CORS 還允許我們添加通配符
*作為允許的外域,這意味著該資源可以被任意外域訪問,所以要注意這種特殊情況
Access-Control-Allow-Origin 是 CORS 機制提供的眾多頭欄位之一,服務器開發人員還可以通過其它頭欄位擴展服務器的 CORS 策略,以允許/禁止某些請求,
另一個常見的回應頭欄位是 Access-Control-Allow-Methods,其指明了跨域請求所允許使用的 HTTP 方法,

在上圖的案例中,只有GET,POST 或 PUT 方法被允許跨域訪問資源,其他 HTTP 方法,例如 PATCH 和 DELETE 都會被阻止,
“如果您想知道其它的 CORS 回應頭欄位是什么以及它們的用途,可以查看此串列,
說到PUT,PATCH 和 DELETE 這幾個 HTTP 方法,CORS 處理這些方法時還有些不同,這些非簡單請求會觸發 CORS 的預檢請求,
4.預檢請求
CORS 有兩種型別的請求:一種是簡單請求(simple request),一種是預檢請求(preflight request),一個跨域請求到底是簡單的的還是預檢的,取決于一些 request header,
當請求是 GET 或 POST 方法并且沒有任何自定義 Header 欄位時,一般來說就是個簡單請求,除此之外的任何請求,諸如 PUT,PATCH 或 DELETE 方法,將會產生預檢,
“如果你想知道一個請求必須滿足哪些要求才能成為簡單請求,可以查看 MDN 簡單請求相關的檔案,
說了這么多,「預檢請求」到底是什么意思?下面我們就來探討一下,
1?? 在發送實際請求之前,客戶端會先使用 OPTIONS 方法發起一個預檢請求,預檢請求的 Access-Control-Request-* 中包含有關我們將要處理的實際請求的資訊:
首部欄位 Access-Control-Request-Method告知服務器,實際請求要用到的方法是什么首部欄位 Access-Control-Request-Headers告知服務器,實際請求將附帶的自定義請求首部欄位是什么
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

2?? 服務器接收到預檢請求后,會回傳一個沒有 body 的 HTTP 回應,這個回應標記了服務器允許的 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
3?? 瀏覽器收到預檢回應,并檢查是否應允許發送實際請求,

“??:上圖預檢回應漏了
Access-Control-Allow-Headers: Content-Type
4?? 如果預檢回應檢測通過,瀏覽器會將實際請求發送到服務器,然后服務器回傳我們需要的資源,

如果預檢回應沒有檢驗通過,CORS 會阻止跨域訪問,實際的請求永遠不會被發送,預檢請求是一種很好的方式,可以防止我們訪問或修改那些沒有啟用 CORS 策略的服務器上的資源,
“?? 為了減少網路往返次數,我們可以通過在 CORS 請求中添加
Access-Control-Max-Age頭欄位來快取預檢回應,瀏覽器可以使用快取來代替發送新的預檢請求,
5.認證
XHR 或 Fetch 與 CORS 的一個有趣的特性是,我們可以基于 Cookies 和 HTTP 認證資訊發送身份憑證,一般而言,對于跨域 XHR 或 Fetch 請求,瀏覽器不會發送身份憑證資訊,
盡管 CORS 默認情況下不發送身份憑證,但我們可以通過添加 Access-Control-Allow-Credentials CORS 回應頭來更改它,
如果要在跨域請求中包含 cookie 和其他授權資訊,我們需要做以下操作:
XHR 請求中將 withCredentials欄位設定為trueFetch 請求中將 credentials設為include服務器把 Access-Control-Allow-Credentials: true添加到回應頭中
// 瀏覽器 fetch 請求
fetch('https://api.mywebsite,com.users', {
credentials: "include"
})
// 瀏覽器 XHR 請求
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;
// 服務器添加認證欄位
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true

把上面的作業做好后,我們就可以在跨域請求中包含身份憑證資訊了,
6.總結
CORS Error 一定程度上會讓前端開發很頭疼,但是遵循它的相關規定后,它可以讓我們在瀏覽器中進行安全的跨域請求,
同源策略和 CORS 的知識點有很多,本文只講了一些關鍵知識點,如果你想全面學習 CORS 的相關知識,我推薦你查閱MDN 檔案和 W3C 規范,這些一手知識是最準確的,
7.最后
這篇文章就到此結束了,如果覺得不錯的話一定要點贊鼓勵一下哦,祝大家學習進步,作業順利!
如果想要學習更多非筆記式的 HTTP 知識,可以看看我之前寫的舊文:
X-Forwarded-For 拿到的就是真實 IP 嗎? HTTP 請求中,空格應該被編碼為 %20 還是 + ? HTTP 的這幾個坑你都踩過嗎?
最后推薦一波我的個人號:鹵蛋實驗室(egglabs),會更新一些前端技術與圖形學相關的文章,獨創不灌水,歡迎大家關注,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/18361.html
標籤:JavaScript
上一篇:JAVA 如何訪問sharepoint 檔案庫中檔案內容?
下一篇:浮點數相關
