後端工程師面試考什麼 - 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 不相同(wwwapi),所以不是同源。

又例如 2 個網址 C 與 D:

  • C https://www.example.com:3000
  • D https://www.example.com

這 2 者的 port 不相同(3000443),所以不是同源。

同源政策 (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 ,並且該請求會附上 originx-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 章節:

  1. GET, POST, HEAD 以外的 HTTP 方法
  2. 使用不是瀏覽器自動設定的 HTTP headers
  3. 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-TrackingAuthorization 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

FOLLOW US

對抗久坐職業傷害

研究指出每天增加 2 小時坐著的時間,會增加大腸癌、心臟疾病、肺癌的風險,也造成肩頸、腰背疼痛等常見問題。

然而對抗這些問題,卻只需要工作時定期休息跟伸展身體即可!

你想輕鬆改變現狀嗎?試試看我們的 PomodoRoll 番茄鐘吧! PomodoRoll 番茄鐘會根據你所設定的專注時間,定期建議你 1 項辦公族適用的伸展運動,幫助你打敗久坐所帶來的傷害!

贊助我們的創作

看完這篇文章了嗎? 休息一下,喝杯咖啡吧!

如果你覺得 MyApollo 有讓你獲得實用的資訊,希望能看到更多的技術分享,邀請你贊助我們一杯咖啡,讓我們有更多的動力與精力繼續提供高品質的文章,感謝你的支持!