後端工程師面試考什麼 - CORS (Cross-Origin Resource Sharing)
Posted on Apr 19, 2023 in 後端面試準備 by Amo Chen ‐ 5 min read
隨著前後端技術演進,姑且不論是否採用微服務(microservice)架構,前後端分離成為越來越常見的開發模式,例如前端採用 NEXT.js 等框架,或者編譯成靜態網頁(static site)部署在雲端,所需要的後端 API 功能則是呼叫部署在另一個網域(domain)的伺服器,例如下圖:
不過採用此種前後端分離的架構,就需要理解何謂 CORS(Cross-Origin Resource Sharing),才能讓前端正常運作。
p.s. 通常有用到前後端分離架構的公司,就有可能問到何為 CORS 。
同源政策 (Same-Origin Policy)
談 CORS 之前,必須先知道什麼是同源(Same-Origin)。
Same-Origin 是指網頁的協定(protocol, 例如 HTTP 或 HTTPS)、主機名稱(hostname)、網域(domain)和通訊埠(port)都相同的情況。
<protocol>://<hostname>.<domain>:<port>
對瀏覽器而言,同一個 origin 被稱為同源 (Same-Origin)。
例如 2 個網址 A 與 B:
- A
https://www.example.com
- B
https://api.example.com
這 2 者的 hostname 不相同(www 與 api),所以不是同源。
又例如 2 個網址 C 與 D:
- C
https://www.example.com:3000
- D
https://www.example.com
這 2 者的 port 不相同(3000 與 443),所以不是同源。
同源政策 (same-origin policy) 是一個重要的安全機制,它防止網頁之間的資源被未經授權共享,保護使用者和網站的安全。只有同源的網頁之間,才能自由地共享資源,不需要任何特殊的權限或跨域限制。
例如,同一個 origin 下的網頁之間可以互相存取彼此的資源,例如使用 AJAX 呼叫同ㄧ個 origin 的 API, 又或者使用 iframe 內嵌同ㄧ個 origin 的網頁。
不過凡事都有例外,當網頁之間需要跨網域或者非同源共享資源時,就需要使用 CORS 。
CORS (Cross-Origin Resource Sharing)
CORS (Cross-Origin Resource Sharing) 是一種機制,允許網頁從不同的來源請求資源。
透過 CORS,一個網頁可以使用來自其他網頁的資源,例如圖片、字體、JavaScript 程式碼或其他資料。
CORS 的運作涉及幾種 HTTP headers,其中最重要的是由後端伺服器發出的 Access-Control-Allow-Origin 。
Access-Control-Allow-Origin
Access-Control-Allow-Origin 是一個由後端伺服器回應的 HTTP header, 用於告知瀏覽器哪些網頁可以請求該資源。
例如,如果一個資源(例如圖片、 API 等)允許從 example.com 發出請求,但是拒絕從其他來源(origin)發出請求,伺服器可以將 Allow-Origins 設定為 example.com。
以下是一個範例:
Access-Control-Allow-Origin: https://example.com
這告訴瀏覽器只有從 example.com 發出的請求能夠存取資源。
如果需要允許所有來源訪問資源,可以將 Access-Control-Allow-Origin 設定為 *
,例如:
Access-Control-Allow-Origin: *
這將允許所有來源訪問該資源,須謹慎使用。
注意: Access-Control-Allow-Origin 最多只能設定一組,無法設定多組,如果你的後端伺服器需要服務來自多組的來源的請求,就需要在後端伺服器的程式碼中檢查來源,並回傳相對應的來源予前端,例如下列 fiber 程式片段:
defaultOrigin := "https://a.examle.com"
origin := ctx.Request.Header.Get("origin")
if (
origin == defaultOrigin ||
origin == "https://b.example.com"
) {
ctx.Response().Header.Set("Access-Control-Allow-Origin", origin)
} else {
ctx.Response().Header.Set("Access-Control-Allow-Origin", defaultOrigin)
}
Access-Control-Allow-Methods
Access-Control-Allow-Methods 是一個後端伺服器回應的 HTTP header, 用於告知瀏覽器該資源支援的 HTTP 方法。
例如該資源只支援 GET、POST 或 PUT ,可以使用以下設定:
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Max-Age
在進行 CORS 之前,在特定條件下,瀏覽器會自動發出一個稱為 preflight 的請求,用以檢查 CORS 協定是否可以運作,它的方法不同於常見的 GET, POST, PUT 等方法,而是使用 OPTIONS
,例如下列代表瀏覽器透過 preflight 詢問後端伺服器是否允許發送 DELETE 請求至 /resource/foo
,並且該請求會附上 origin
與 x-requested-with
2 個 headers:
OPTIONS /resource/foo
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: origin, x-requested-with
Origin: https://foo.bar.org
以下是 Gmail 使用 preflight 請求的真實範例,可以看到 method: OPTIONS
, Access-Control-Request-Method
以及 Access-Control-Request-Headers
3 種 headers:
簡而言之,發出 preflight 的特定條件,以下任一情況符合就算,詳見 Simple Requests 章節:
- GET, POST, HEAD 以外的 HTTP 方法
- 使用不是瀏覽器自動設定的 HTTP headers
text/plain
,multipart/form-data
,application/x-www-form-urlencoded
3 種以外的 Content-Type
但是每次進行 CORS 都要發出 preflight 請求也會耗時,甚至浪費伺服器端的資源,因此 preflight 也設計快取機制,讓瀏覽器不用一直針對相同資源發出 preflight 請求,快取時間預設值為 5 秒。
如果要改變快取時間的預設值,後端伺服器可以透過回應 Access-Control-Max-Age header 告訴瀏覽器改變快取時間,例如:
Access-Control-Max-Age: 3600
不過各家瀏覽器可以設定的最大值不同,建議設定時查一下文件。
Access-Control-Allow-Headers
Access-Control-Allow-Headers 是一個後端伺服器回應的 HTTP header, 用於告知瀏覽器哪些自訂的 HTTP headers 可以被包含在跨域(cross origin)請求中,例如:
Access-Control-Allow-Headers: X-Tracking, Authorization
上面的範例指定了 X-Tracking
和 Authorization
2 個自訂的 HTTP headers 可以被包含在請求中,從而允許跨域請求。
Access-Control-Expose-Headers
Access-Control-Expose-Headers 是一個後端伺服器回應的 HTTP header, 用於告知瀏覽器,後端伺服器回應的請求裡,哪些 headers 可以被存取。
如果後端伺服器在回應 CORS 請求時,有附上客製的 HTTP headers 的話,瀏覽器預設是讀取不到這些 headers 的,預設只有以下的 headers 可以讀取到(詳見 CORS-safelisted response header - MDN Web Docs Glossary: Definitions of Web-related terms | MDN ):
所以當你在後端伺服器加上 1 個客製化 header 時,而且前端需要讀取這個 header 裡的值時,例如 X-Custom-Header
,記得同時設定 Access-Control-Expose-Headers 為:
Access-Control-Expose-Headers: X-Custom-Header
上述告訴瀏覽器可以存取 X-Custom-Header, 如果需要允許多個 headers, 可以用逗號分隔它們:
Access-Control-Expose-Headers: X-Custom-Header, X-Another-Custom-Header
p.s. 當你遇到前端發出 CORS request 之後,不知道為什麼一直讀取不到某些 headers 的值時,記得查一下是否後端伺服器有設定 Access-Control-Expose-Headers
Access-Control-Allow-Credentials
Access-Control-Allow-Credentials 是一個後端伺服器回應的 HTTP header, 用於告知瀏覽器是否允許跨域請求附上認證相關的 headers, 例如 Cookies, HTTP 認證相關的 headers (例如 Authorization
) 或者客戶端的 TLS 憑證。
如果允許送上 Cookie 等標頭,後端伺服器需要回應以下 header:
Access-Control-Allow-Credentials: true
總結
CORS 是一個瀏覽器的安全機制,用於保護網頁資源不被其他來源的請求存取,不過並無法防止瀏覽器以外的程式進行非法存取,但遵循 CORS 規範還是可以基本保護網頁資源不被其他網站給濫用。
加上現代前後端分離的架構,理解 CORS 也成為後端開發不可忽略的一部分。
References
https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
CORS-safelisted response header - MDN Web Docs Glossary: Definitions of Web-related terms | MDN