Skip to content

CORS

跨來源資料共用 CORS (Cross-Origin Resource Sharing)同源政策 (Same-origin policy),是前端開發時常會踩到的坑。當瀏覽器對不同來源發出請求時會建立一個跨域請求(cross-origin HTTP request),例如在 http://domain-a.com HTML 使用一個 src 為 http://domain-b.com/image.jpg 的 img tag,這樣即為一個跨域請求。在一些條件下跨域請求會被瀏覽器阻擋,而無法取得 Response。

基於安全考量,瀏覽器發出的 Request 都要遵守同源政策 (Same-origin policy)。除非服務端使用 CORS 標頭,否則瀏覽器只能請求同源的 HTTP 資源。要特別注意的是網域(domain)、通訊協定(protocol)或通訊埠(port)只要有任一個不同都視為不同源,如 localhost:8080 與 localhost:8000 一樣被視為不同源。

跨域請求一般而言分為簡單請求與預檢請求兩種:

  1. 簡單請求 Simple requests:
    一般而言滿足以下條件的 Request 為簡單請求

    • HTTP Methods:
    • GET
    • HEAD
    • POST
    • Content-Type:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

    除了 Method 有限定外,Content-Type 其實也有限定,所以一般 RESTful 常用的 POST 搭配 application/json 也會被同源政策阻擋。
    若為瀏覽器會直接發出 Request,並取得正常的回應,如下圖: simple request
    Image Source: MDN web docs 2. 預檢請求 Preflighted requests:
    預檢請求分為兩個步驟執行,當瀏覽器發現該 Request 是預檢請求時會先發出一個 OPTIONS Request 並告知實際要發出的 Method 與相關資訊,服務端再根據這些資訊回覆是否可以執行該 Request,瀏覽器確認服務端允許後才會發出真正的 Request,如下圖: preflighted request
    Image Source: MDN web docs
    如果被阻擋就通常會得到 No 'Access-Control-Allow-Origin' header is present on the requested resource.Cross-Origin Request Blocked 這類的錯誤訊息。

CORS 常見的前端 Workaround 通常是使用 JSONP ,但隨著網路服務模式的改變,現在許多服務多以 API 的形式提供,所以也都會有搭配正常的 CORS 讓使用者使用,那些 Workaround 也逐漸沒什麼人在使用了。

而除了 Request 外,網頁的元素操作很多地方都會踩到 Same-origin policy 的坑,如不同網域之間的 iframe 操作 SecurityError: Blocked a frame with origin from accessing a cross-origin frame,其核心概念就是如果不同源,有很多事情瀏覽器都會基於安全考量都擋掉。

到這邊可以發現 CORS 與 Same-origin policy 都是瀏覽器負責檢核,若單純指使從後端發 Request 完全不用考慮這些問題,因此也有很多 Workaround 是在後端作一個跳板間接取得資源,或直接用 cors-anywhere 這類第三方服務當跳板。

CORS 其實還有很多詳細內容,但這邊跳過了一些細節僅簡單說明原理,詳細可以參考 MDN web docs - 跨來源資源共用(CORS),十分值得一讀。

Backend CORS 設定

前端是否能執行跨域請求完全是依賴於後端是否有設定 CORS,Enable CORS 列出了一些常見的後端 Server 設定方法。以下針對 Python 常用的 Django 與 Flask 後端框架說明設定方法。

Django

在使用 Django REST framework 做為網站後端的時,前端頁面通常會是不同來源,可以使用 django-cors-headers 解決 CORS 的問題。

pip install django-cors-headers
# settings.py
INSTALLED_APPS = (
    ...
    'corsheaders',
    ...
)

MIDDLEWARE_CLASSES = (
    ...
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...
)

CORS_ORIGIN_ALLOW_ALL = True # 允許所有的跨站請求, 白名單不會被啟用

# 白名單設定
CORS_ORIGIN_WHITELIST = (
    'http://localhost:9000',
    'http://example.com',
    ...
)

Flask

Flask 作為後端時,可以使用 Flask-CORS 解決 CORS 的問題。

pip install flask-cors
from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

@app.route("/")
def helloWorld():
  return "Hello, cross-origin-world!"

Reference:

  1. MDN web docs - 跨來源資源共用(CORS)
  2. MDN web docs - 同源政策 (Same-origin policy)
  3. 深入認識跨域請求
  4. 更安全的請求標頭 - Fetch Metadata Request Headers

Comments