網頁開發常見之 CORS 錯誤原因與 Express 解決辦法

CookSen
SWF Lab
Published in
8 min readApr 16, 2023

--

前言

相信有在開發前後端的人對 CORS (Cross-Origin Resource Sharing) 絕對不陌生,特別是他跳出的錯誤訊息:

Access to fetch at ‘https://blablabla:3001/' from origin ‘http://localhost:3000' has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

而 CORS 其實是一種安全機制,用於限制在網頁上的跨域請求。雖然這項技術為網站提供了更好的安全性和可靠性,但在實際應用中,有時候會因為 CORS 錯誤而出現各種問題,如請求失敗或跨域資源無法正常載入等。因此,了解 CORS 錯誤的原因和解決辦法對於網頁開發人員來說是非常重要的。在這篇文章中,我們將探討 CORS 錯誤的原因和解決辦法,幫助讀者更好地了解這個議題並解決相關問題。

什麼是 CORS 和 CORS 跨域錯誤?

這裡就要從 CORS 的全名,Cross-Origin 中的 Origin 說起,Origin 是指發出跨域請求的網頁的源網域。它由協議(http:// 或 https://)、主機名稱和 Port 組成。例如,一個 Origin 可以是 https://example.com 或 http://localhost:3000。

而跨域請求是指從另一個源(Origin)發出的請求,例如從 https://api.example.com 發出到 https://example.com 的請求,可以看出這裡的主機名稱就不同了;或是從 http://localhost:3000 發送到 http://localhost:3001 也會是跨域請求,因為 Port 不同了。

而當跨域請求產生時,可以分為簡單請求與非簡單請求:

簡單請求:

滿足以下所有條件的跨域請求:

  • 使用GET、POST、HEAD方法之一。
  • Content-Type的值只能是text/plain、multipart/form-data、application/x-www-form-urlencoded中的一種。
  • 請求標頭中沒有使用自定義標頭。

對於簡單請求,瀏覽器直接發送跨域請求,並在請求標頭中加上一個Origin字段,表示這個請求的發起源,服務器收到這個請求後,在返回的標頭中加上一個Access-Control-Allow-Origin字段,指定允許哪些源發起跨域請求,瀏覽器接收到服務器的回應後,會根據Access-Control-Allow-Origin的值決定是否允許該請求。

實際舉例的話就例如 get 一個圖片,或是一段 HTML 文字等,這類 api 用法就不會遇到 cors 阻擋。

非簡單請求:

不滿足簡單請求條件的跨域請求,例如使用PUT、DELETE等方法,或者Content-Type為application/json等自定義類型,或者使用自定義請求標頭等。

對於非簡單請求,瀏覽器會在發送實際請求之前,會先發送一個預檢請求,即OPTIONS請求,這個請求中包含了Access-Control-Request-Method、Access-Control-Request-Headers等標頭,用於告訴服務器該實際請求中使用的HTTP方法和自定義標頭。服務器接收到這個預檢請求後,檢查該請求是否允許跨域請求,如果允許,就在回應標頭中加上Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers等標頭,告訴瀏覽器允許跨域請求。然後瀏覽器才會發送實際的跨域請求。

有點類似瀏覽器會協助保護被要求資料的一端,並幫忙兩者溝通。

而若要更進一步討論為什麼要分成簡單請求與非簡單請求,我個人是理解為可以從你是否能讀取這個資料當作判斷依據,像是我用簡單請求網路上的圖片、字型等資料,最後呈現的只會是瀏覽器,前端程式能取用的資源有限,因此就不像非簡單請求一樣需要 CORS 擋在中間。

開發網站時,常常會遇到前端與後端的Port不同,或是前後端各自擁有獨立的網域的狀況,當呼叫api時,就可能會遇到CORS錯誤。讀完前述內容後,就可以很容易理解這個錯誤出現的原因了吧!

為什麼需要 CORS 擋在中間?

CORS 的重要性在於它允許瀏覽器強制執行同源策略(Same Origin Policy)。同源策略是一種安全措施,可防止惡意腳本訪問它不應該訪問的資源。

如果沒有 CORS,惡意腳本可以向另一個網域的服務器發出請求,並訪問該頁面使用者未打算訪問的資源。也就是說今天你可以隨意架設一個前端戳你目標網域的所有 Port,取得被攻擊者所有資料,這當然會衍生出許多問題,因此就需要 CORS 對來源做把關。

該如何解決 CORS 錯誤呢?

解決 CORS 問題有很多種方法,分別可以從前端與後端下手,但大多從前端解決的方法都無法真正解決這個問題,所以這篇文章會主要介紹後端的解決辦法,並使用 Express 後端架構舉例。

預設情況下,Express 不允許跨域存取。換句話說,如果未設置 CORS 相關設置,則無法存取。不過,官方提供了一個易用的中介軟體來實作 CORS,讓我們一起來看看如何使用吧!

首先安裝:

npm install cors
npm install @types/cors --save-dev

再來在 express 專案中引入:

var express = require('express');
var cors = require('cors');
var app = express();

app.use(cors());

但這時候的 CORS 預設是全部開放,會有點危險,進一步設定可以像以下步驟:

const corsOptions = {
origin: [
'https://www.example.com',
'http://localhost:3001',
],
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
allowedHeaders: ['Content-Type', 'Authorization'],
};

app.use(cors(corsOptions));

在 origin 這個 array 中新增你要許可的來源。

在 methods 設置可存取的方法,可以用逗號隔開也可以使用像 origin 的 string array 宣告。

在 allowedHeaders 中,這裡設定了['Content-Type', ‘Authorization’]。這表示只有在請求標頭中包含這兩個標頭時,才會允許跨域請求。

結語:

CORS 錯誤是我自己本身開發前後端時很常遇到的問題,特別是當先把前後其中一端架到伺服器後,要測試時就會看到這個讓人頭痛的錯誤。

希望這篇文章可以幫助正在頭痛或是不太懂背後原理的人們,也讓自己以後有個筆記可以回來複習複習。

Reference:

--

--

CookSen
SWF Lab
Writer for

An EE student at National Taiwan University. A junior engineer who likes both software and hardware.