CORS過程的介紹以及跨域問題

CORS過程的介紹以及跨域問題

參考文章:https://mp.weixin.qq.com/s/Re1fvKKzi-rPpu6SmpqTJA

文中的動(dòng)圖以及部分總結(jié)語句均引用與參考文章。我這里只是為了讓自己理解深刻一點(diǎn)而嘗試用自己的語言去總結(jié)一遍。推薦閱讀原文


說到跨域問題,我們最常見的就是在我們向服務(wù)器發(fā)起請(qǐng)求的時(shí)候會(huì)遇到。比如說如下情況

image

假設(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ò)。如下圖

image

而以上兩種情況出現(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)求也算是跨域的。

image

所以我們同樣可以看得出來。同源策略會(huì)限制以下三種行為:

  1. Cookie,LocalStorage,和IndexDB 訪問受限
  2. 無法操作跨域DOM(常見的如 iframe)
  3. 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)操作。

image

而我們當(dāng)然不希望這種情況發(fā)生,這個(gè)時(shí)候同源策略就起到有效的保護(hù)作用。因?yàn)樗_保了我們只能訪問同一站點(diǎn)的資源

image

好了,接下來要說的就是CORS了。

瀏覽器的CORS

瀏覽器出于安全的考慮,會(huì)限制從腳本內(nèi)發(fā)起的跨域HTTP請(qǐng)求。(注意是腳本內(nèi))例如XHR 和 Fetch 就遵循同源策略。這意味著使用 API 的Web應(yīng)用程序只能從加載應(yīng)用程序的同一個(gè)域請(qǐng)求HTTP資源。

image

在日常的開發(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è)額外的字段 OriginOrigin 標(biāo)記了請(qǐng)求的站點(diǎn)來源:

    GET https://api.website.com/users HTTP/1/1
    Origin: https://www.mywebsite.com -> 瀏覽器自己攜帶的
image

為了使瀏覽器允許訪問跨域資源,服務(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)被允許跨域訪問資源的。

  1. 如果我們有服務(wù)器的開發(fā)權(quán)限,我們可以給 https://www.mywebsite.com 加上訪問權(quán)限,只需要把這個(gè)域名添加到 Access-Control-Allow-Origin
image

現(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: [{...}]}
image
  1. 收到服務(wù)器返回的response后,瀏覽器中的CORS機(jī)制會(huì)檢查 Access-Control-Allow-Origin 的值是否等于 request 中 Origin的值
image
  1. 瀏覽器通過校驗(yàn)之后,前端成功接收到跨域站點(diǎn)的資源

而相反,如果對(duì)比Access-Control-Allow-OriginOrigin 的值不一致的時(shí)候,瀏覽器會(huì)拋出一個(gè) CORS Error的報(bào)錯(cuò)信息。

image

當(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 方法。

image

如上圖中,只有請(qǐng)求方法為 GETPOSTPUT 方法被允許跨域訪問資源。其他的HTTP方法,例如 PATCHDELETE 都會(huì)產(chǎn)生預(yù)檢。

這里就引申出一些別的內(nèi)容,比如說預(yù)檢

什么是預(yù)檢呢?

預(yù)檢 = 預(yù)請(qǐng)求(options請(qǐng)求)
  1. 在發(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
image
  1. 服務(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
  1. 瀏覽器收到預(yù)請(qǐng)求的響應(yīng),并檢查是否應(yīng)允許發(fā)送實(shí)際請(qǐng)求
image

上圖漏了 Access-Control-Allow-Headers: Content-Type

  1. 如果預(yù)請(qǐng)求通過了,瀏覽器會(huì)將實(shí)際請(qǐng)求發(fā)送到服務(wù)器,然后服務(wù)器會(huì)返回我們想要的資源
image

而此處有一個(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ǔ)充

  1. 認(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
image

做到了上面幾點(diǎn)之后,就能在跨域請(qǐng)求中攜帶身份憑證了。


補(bǔ)充

  1. 我們上面提到可以通過設(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的值.

  1. 簡單請(qǐng)求不會(huì)觸發(fā)預(yù)請(qǐng)求,但是會(huì)根據(jù)請(qǐng)求響應(yīng)的報(bào)文去判斷是否跨域了(即對(duì)比origin,和Access-Control-Allow-Origin的值是否一致)

  2. 簡單請(qǐng)求這種,實(shí)際是已經(jīng)發(fā)去了的請(qǐng)求,也就是說請(qǐng)求已經(jīng)到了服務(wù)器了,然后返回來了。只是瀏覽器攔截了響應(yīng),而并不是攔截了請(qǐng)求本身


以上就是全部內(nèi)容了

補(bǔ)充參考:http://www.goodpm.net/postreply/csharp/1010000008959813/AccessControlAllowOrigin%E5%A6%82%E4%BD%95%E8%AE%BE%E7%BD%AE%E5%A4%9A%E4%B8%AA%E5%80%BC%E5%91%A2.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。