跨來源資源共用(CORS)

故事背景

今日上班的時候,隔壁的小王跑來跟我說,目前線上遇到CORS的問題,深入了解後才曉得在後端的工程人員對於CORS的認識不深,因此在進行新環境部署的時候缺少環境的設定檔,面對總是有類似的狀況一再發生,是否有什麼方式可以改善。

同源策略

要說起CORS的由來,主要還是在於安全性考量,在瀏覽器上大部份是採用同源政策(Same-origin policy)限制了不同網域資源間的活動。因此當我們API站的網址、埠號、協定不一致的時候,通常我們會在開發者工看到下面的錯誤訊息:

CORS

隨著目前網站提供的功能與內容的豐富度越來越高,經常會有需要存取第三方資源的狀況,因此大部份都會透過一些跨域存取的方法達到請求資源的目的,CORS就是其中一種可以達成存取資源方法。伺服器會透過preflight request機制使用OPTIONS方法發出一個請求,確認伺服器是否支援跨域請求以及相關內容,當伺服務允許之後,才會實際發送資源請求的動作,並告知客戶端是否有需要相關的認證資訊(Cookie、Header)之類的。

解法方法

根據伺服器的不同,通常很容易在網路上找到各自對應的設定方式,這邊我以大家常使用的nginx伺服器設定進行解說,如果想要更進一步的了解,可以參考阮一峰大大的這篇日誌

nginx的設定檔加入下述參述

1
2
3
4
5
6
7
8
9
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET,POST,PUT,DELETE,OPTIONS';
add_header Access-Control-Allow-Headers 'Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control';

if ($request_method = 'OPTIONS') {
return 204;
}
}

Access-Control-Allow-Origin

當設定為 * 即表示伺服器接受跨域請求。

Access-Control-Allow-Methods

這邊需指定伺服器允許的請求方式有哪些,一般常見的的會是 OPTIONSGETPOST,可以根據需要進行設定。

Access-Control-Allow-Headers

這邊需指定伺服器可接受的檔頭內容,可以根據需要進行設定。

withCredentials

CORS請求在預設上不會涉及Cookie相關認證的處理,如果需要客端將Cookie發送至伺服器除

xhr上需要設定withCredentials參數

1
2
3
4
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('GET', 'https://www.example.com/info', true);
xhr.send();

伺服器也需要指定檔頭的設定

1
add_header 'Access-Control-Allow-Credentials' 'true';

測試CORS設定是否生效

要曉得伺服務是否支援CORS,經由正式的客端環境透過瀏覽器發求資源請求是比較正規的方式,但是理解原理後,也可以透過寫一些腳本來輔助我們進行新環境的檢查,下面舉一個簡單請求測試範例供參考。

使用curl指令測試氣象資料開放平臺的API請求

1
2
3
curl -I -X OPTIONS \
-H "Origin: http://example.com" \
"https://opendata.cwb.gov.tw/api/v1/rest/datastore/F-C0032-001?Authorization={授權碼}"

從 Response Header 看到Access-Control-Allow-Origin 欄位設定是否正確

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Date: Wed, 31 Aug 2022 03:07:52 GMT
Connection: keep-alive
Allow: GET,HEAD
Access-Control-Allow-Origin: *
Strict-Transport-Security: max-age=16070400; includeSubDomains; preload
Set-Cookie: TS01a5ae52=0107dddfef9ec54bca15dea78f784755ae5faeeb1563e9155cd3bf5d2a9c2c142332f4e99fe8b5eca4a85c0982aa2fbf202c696e40; Path=/; Domain=.opendata.cwb.gov.tw
Transfer-Encoding: chunked

如果非簡單請求的狀況,可以進一步檢視回應中Access-Control-Allow-MethodsAccess-Control-Allow-HeadersAccess-Control-Allow-Credentials這些字段是否與伺服器上的設定是相符的。