不是全部的 No 'Access-Control-Allow-Origin' header... 都是跨域問題 - 記一次圖片上傳踩坑

什麼是跨域 ?

跨域這個問題你們並不陌生,這也是面試的高頻問題,不少人都背過,什麼由於同源策略啊,CORS 啊等等,跨域的標緻就是瀏覽器控制檯出現 Access to XMLHttpRequest at 'https://xxx.xxx.com' from origin 'https://xxx.xxx.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.html

首先,只有 Web 瀏覽器纔會產生跨域,這是由於瀏覽器的同源策略在限制。同源策略就是 [域名(又稱爲主機 host),端口(port),協議(protocol)] 要統一,不然就會被瀏覽器斷定爲跨域,而後拒絕請求。可是嚴格的說,瀏覽器並非拒絕全部的跨域請求,實際上拒絕的是跨域的讀操做。前端

同源策略並非很差,它在必定程度上保證了瀏覽器的安全,只是沒法適應現代的潮流,在工程服務化後,不一樣職責的服務分散在不一樣的工程中,每每這些工程的域名是不一樣的,但一個需求可能須要對應到多個服務,這時便須要調用不一樣服務的接口。node

哪些狀況下會產生跨域 ?

URL                             說明                是否容許通訊
http://www.a.com/a.js
http://www.a.com/b.js           同一域名下            容許

http://www.a.com/lab/a.js
http://www.a.com/script/b.js    同一域名下不一樣文件夾    容許

http://www.a.com:8000/a.js
http://www.a.com/b.js           同一域名,不一樣端口      不容許

http://www.a.com/a.js
https://www.a.com/b.js          同一域名,不一樣協議      不容許

http://www.a.com/a.js
http://70.32.92.74/b.js         域名和域名對應ip        不容許

http://www.a.com/a.js
http://script.a.com/b.js        主域相同,子域不一樣      不容許(cookie這種狀況下也不容許訪問)

http://www.a.com/a.js
http://a.com/b.js               同一域名,不一樣二級域名(同上) 不容許(cookie這種狀況下也不容許訪問)

http://www.cnblogs.com/a.js
http://www.a.com/b.js           不一樣域名            不容許
複製代碼

什麼是 CORS

跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓運行在一個 origin (domain) 上的 Web 應用被准許訪問來自不一樣源服務器上的指定的資源。當一個資源從與該資源自己所在的服務器不一樣的域、協議或端口請求一個資源時,資源會發起一個跨域 HTTP 請求。 好比,站點 http://domain-a.com 的某 HTML 頁面經過 <img> 的 src 請求 http://domain-b.com/image.jpg。網絡上的許多頁面都會加載來自不一樣域的 CSS 樣式表,圖像和腳本等資源。
出於安全緣由,瀏覽器限制從腳本內發起的跨源 HTTP 請求。 例如,XMLHttpRequest 和 Fetch API 遵循同源策略。 這意味着使用這些 API 的 Web 應用程序只能從加載應用程序的同一個域請求 HTTP 資源,除非響應報文包含了正確 CORS 響應頭。ios

跨域資源共享( CORS )機制容許 Web 應用服務器進行跨域訪問控制,從而使跨域數據傳輸得以安全進行。現代瀏覽器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch )使用 CORS,以下降跨域 HTTP 請求所帶來的風險。nginx

不過呢,並不必定是瀏覽器限制了發起跨站請求,也多是跨站請求能夠正常發起,可是返回結果被瀏覽器攔截了。面試

在「上古時代」,解決跨域有不少黑科技,什麼 JSONP 啊,window.name 啊,document.domain 啊等等都用上了,但現代用的基本都是CORS跨域,因此上述方法在這裏就不討論了。後端

由上述可知,實現 CORS 通訊的關鍵是服務器。只要服務器實現了 CORS 接口,就能夠跨源通訊。跨域

雖然工做重心在後端,可是做爲前端,也要了解這方面的知識,不然就會出現204預請求,而後後端沒處理,就甩鍋給前端的狀況。瀏覽器

下面介紹一些 CORS 過程當中常見的 HTTP 響應頭(Response Headers)。安全

什麼是 Access-Control-Allow-Origin

這是 HTTP 響應首部中的一個字段,

具體格式是: Access-Control-Allow-Origin: <origin> | *

其中,origin 參數的值指定了容許訪問該資源的外域 URI。對於不須要攜帶身份憑證的請求,服務器能夠指定該字段的值爲通配符,表示容許來自全部域的請求。

若是服務端指定了具體的域名而非*,那麼響應首部中的 Vary 字段的值必須包含 Origin。這將告訴客戶端:服務器對不一樣的源站返回不一樣的內容。例如:

// 只響應來自 http://mozilla.com 的請求
Access-Control-Allow-Origin: http://mozilla.com
複製代碼

什麼是 Access-Control-Allow-Methods

該字段必需,它的值是逗號分隔的一個字符串,代表服務器支持的全部跨域請求的方法。注意,返回的是全部支持的方法,而不單是瀏覽器請求的那個方法。這是爲了不屢次"預檢"請求。

具體格式是:Access-Control-Allow-Methods: <method>[, <method>]*

什麼是 Access-Control-Allow-Headers ?

可支持的請求首部名字。請求頭會列出全部支持的首部列表,用逗號隔開。

若是瀏覽器請求包括Access-Control-Request-Headers字段,則Access-Control-Allow-Headers字段是必需的。它也是一個逗號分隔的字符串,代表服務器支持的全部頭信息字段,不限於瀏覽器在"預檢"中請求的字段。

具體格式是:Access-Control-Allow-Headers: <header-name>[, <header-name>]*

什麼是 Access-Control-Allow-Credentials

該字段可選。它的值是一個布爾值,表示是否容許發送 Cookie。默認狀況下,Cookie 不包括在 CORS 請求之中。設爲 true,即表示服務器明確許可,Cookie 能夠包含在請求中,一塊兒發給服務器。

瀏覽器的正常請求和迴應

一旦服務器經過了"預檢"請求,之後每次瀏覽器正常的 CORS 請求,就都跟簡單請求同樣,會有一個 Origin 頭信息字段。服務器的迴應,也都會有一個 Access-Control-Allow-Origin 頭信息字段。

好了,鋪墊完成,接下來要說說我踩的坑了。

問題實記

項目背景

公司因歷史遺留,同時存在3種後端語言:Java,PHP,Node.js,由於後端同事都不懂 Node.js,因此 Node 項目一直是前端維護。老項目用的是 Koa 框架,以前我在上面寫邏輯,上傳圖片是沒有問題的,直到我用了 Nest 框架重構,將後臺管理系統的邏輯拆分解耦出來。

踩坑過程

新項目一切請求都正常,本地跑的時候也正常,可是到了線上,只有上傳圖片不正常。每次上傳都會預請求一次 204 OPTIONS,這一步沒問題,但接下來的 POST 請求就有問題了:

我一看控制檯的信息,結合多年的開發經驗(其實並無),這不就是跨域嘛,因而看代碼,main.ts 中已經有了 app.enableCors(),百思不得其解,因而 Google,發現也有人遇到過相似的問題,說是 Nest 某些版本在線上環境沒法正確使用 CORS,而後就造成了下列代碼:

可是,並無什麼卵用,調試了好一下子才注意到響應頭是 nginx 返回的,而後就像發現新大陸同樣屁顛屁顛的找運維老大:代碼裏面已經設置了 CORS 跨域了,是否是被 nginx 攔截了?(公司的服務器經過 nginx 作負載均衡,而後纔到 node)

而後運維老大很配合的幫我配置了 nginx 的跨域,而後就悲劇了,連 OPTIONS 都過不去😂:

控制檯的大體意思是 CORS 規則衝突了,只能使用一種。

因此這個問題其實和 nginx 沒有什麼關係,運維大佬看了訪問日誌,說是 OPTIONS 請求是響應了的,可是到 POST 請求的時候就斷開鏈接了,而後讓我試試直接訪問端口,因而控制檯又出現了以下信息:

唔,大體意思是,源頭是 https 協議的話,就不能請求 http 協議的資源。

因而我把網站上的 https 換成 http,就出現了以下信息:

我就以爲很奇怪,由於本地開發的時候,是能正常上傳圖片的。惟一不一樣的地方就在於,線上使用的是 pm2 進程管理工具,而我本地用的是自帶的 nodemon。爲了驗證個人猜想,因而本身的電腦上也裝了 pm2,而後跑起來,而後就。。。Bug 果真復現了。

因而讓運維大佬不用 pm2 直接用 nodemon 啓動試試,而後折騰我好久的跨域問題就「解決了」。爲何打引號呢,由於運維大佬並非很想用 nodemon 來管理進程,主要是若是服務器宕機,不能自動拉起,戰役還遠沒有結束。

而後我就圍繞着 pm2 繼續探索,把官方文檔看了個遍,也沒找到關鍵點,鬱悶之下,只好檢查的 pm2 啓動配置,而後注意到這個:

這是當初我爲了輸出日誌的時候,更新了文件,防止被 pm2 監聽到,致使服務一直重啓所作的措施。而後就想到了,我上傳圖片的時候,是先在硬盤保存,而後讀取 Buffer 流,而後再上傳到 oss,最後刪掉硬盤的圖片,核心代碼以下:

因此我就在想,是否是由於上傳的時候,存本地的圖片觸發了 pm2 的監聽,致使服務重啓,因此就會報 net::ERR_CONNECTION_RESET,因而我改爲了臨時目錄 const uploadCachePath = '/tmp/assets/uploads'; (Mac OS、Linux 都有這個目錄),而後 pm2 啓動,上傳,成功。

至此,折磨了我近一週的 Bug 終於修復,結果和跨域沒有半毛錢關係。

插曲

有讀者可能注意到 /tmp/assets/uploads 路徑,要是同事用的是 Windows 系統開發咋辦?這個我天然也想到了,因而改了 pm2 的啓動項:

但是不管怎樣改,依然會觸發上述 Bug,由於服務器同時跑着 2 個 Node 項目,另外一個項目也有本身的 pm2 啓動項,因此感受這個配置被另外一個覆蓋了,pm2 彷佛是全局的。若是有其餘大神深刻了解過 pm2 的能夠指點一下。

因此和運維大佬討論了一下,給我開了權限,就暫時用這個臨時目錄,待之後找到更好的解決方案再優化,反正目前這個項目也只有我一人在維護。

遇到的其餘場景

1. 服務器宕機

就在我剛找到解決方案的時候,我帶的小弟跑過來問我是否是動了配置文件,老項目怎麼都跨域了。我去看他的控制檯,確實有 Access to ... has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. 的信息,因爲剛踩的坑,況且我也沒動過配置文件,因此以爲這確定不是跨域問題。而後我看了服務器的日誌,發現一直在重啓,由於有個模塊他沒同步上去,只同步了路由文件,致使路由找不到對應的函數,服務就一直在報錯重啓,根本就沒處理請求。因而讓他把代碼從新上傳,問題解決。

因此我的猜想(由於沒有權限看服務器的配置),此次跨域是 nginx 代理的時候,由於訪問不到 Node 服務,因此天然而然地就讀不到 CORS 配置,而後報跨域錯誤。

2. Axios 的自行檢查

Axios 建立實例時,有個字段須要注意:

若是不設爲 false,則會獲得下面報錯:

緣由就是前面提到的 Access-Control-Allow-Credentials 字段,CORS 請求默認不發送 Cookie 和 HTTP 認證信息。若是要把 Cookie 發到服務器,一方面要服務器贊成:

Access-Control-Allow-Credentials: true

另外一方面,開發者必須在 AJAX 請求中打開 withCredentials 屬性,也就是 Axios 默認打開的 withCredentials: true

不然,即便服務器贊成發送 Cookie,瀏覽器也不會發送。或者,服務器要求設置 Cookie,瀏覽器也不會處理。

可是,若是省略withCredentials設置,有的瀏覽器仍是會一塊兒發送 Cookie。這時,須要顯示關閉 withCredentials: false

須要注意的是,若是要發送 Cookie,Access-Control-Allow-Origin 就不能設爲星號,必須指定明確的、與請求網頁一致的域名。同時,Cookie 依然遵循同源政策,只有用服務器域名設置的 Cookie 纔會上傳,其餘域名的 Cookie 並不會上傳,且(跨源)原網頁代碼中的 document.cookie 也沒法讀取服務器域名下的 Cookie。

總結

由上述能夠總結,在後端配置了 CORS 的狀況下,還會形成 Access to ... has been blocked by CORS policy ... 的狀況大體有:

  1. 服務器忽然重啓,致使代理服務器轉發中斷;
  2. 服務器宕機,致使代理服務器讀不到 CORS 配置;
  3. Axios 等請求插件設置了withCredentials: true,致使先行驗證並攔截了請求;

有些時候,瀏覽器控制檯給出的錯誤信息,不必定能真正地指出問題的所在,作爲前端,還須要多去了解一些更本質的東西。

參考資料

《跨域資源共享 CORS 詳解》- 阮一峯的網絡日誌

《前端跨域整理》

《HTTP 權威指南 - O'REILLY》

·

相關文章
相關標籤/搜索