你是否有在開發網站時遇過 CORS 的錯誤?
像是架了一個後端的 API Server,但是在開發前端時送出 Request 卻遇到類似下面的錯誤
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://locahost:6789
什麼叫做 Cross-Origin,為何 Request 被 Block 住了?但是我用 Postman 這樣的工具明明就可以成功得到 Response 呀!
今天就讓我們來談談網站開發該懂的 CORS。
所謂 CORS 是 Cross-Origin Resource Sharing(跨來源資源共用)的簡稱,大部分的開發者多少都聽過這個很繞口的機制,也知道這是在網頁送出請求後,因為某種原因讓你收不到回覆的安全機制。
我們先來看看這個情境:伺服器在 Domain A,用戶透過瀏覽器查閱 Domain B 的網頁,但在 Domain B 的網頁中送出了一個往 Domain A 的請求。在沒有額外設定的情況下,以下兩種狀況哪個正確?
在 Domain A 的伺服器會拒絕從 Domain B 的網頁來的請求
在 Domain A 的伺服器會回覆請求,但在 Domain B 頁面的瀏覽器會拒絕此回覆
答案是 2,拒絕者是瀏覽器而非伺服器。我們可以試著用非瀏覽器的程式對伺服器送出請求,例如用 Postman 模擬從 Domain B 送出請求,結果發現能成功拿會 Response,這便代表伺服器實際上是有回覆的。
*Request-Response in different domain
為何?難道在 Domain A 的 Server 不該阻擋不是同個 Domain 的請求嗎?可能不該,而且也無法。
這其實便關於我們前面提過的同源政策,不該的原因是提供 API 服務的伺服器原本就有可能接收來自不同網站的請求,例如提供第三方登入的 Auth Server 就是一個例子,總不能只容許 Auth Server 接收相同 Domain 的網頁請求吧;而無法的原因則是來源頁面的 Domain 在傳輸封包的過程中,僅僅只是加上一個叫做 Origin 的 Header,我們在開發時使用的 Postman 就能夠輕鬆修改這個 Header 來模擬從某網頁送出的請求。
因此,瀏覽器就負起這個責任,來擋住和當前瀏覽 Domain 不同來源的回覆。瀏覽器會預設你所要送出請求 Server 的 Domain 要和正在瀏覽的相同,才會接受伺服器的回覆,這是為了預防有惡意的 Script 將當前網頁的一些機密資訊。
我們知道瀏覽器可以儲存當前網頁 Domain 相關資訊在 Cookies 或 Local Storage 中,例如登入的 Token,並在送出請求時攜帶此 Token 來維持登入狀態。此時惡意 Script 就可以透過已登入的資訊來獲取資訊。
等到拿到這些機敏資料後,再把這些資料傳到駭客自己的伺服器中,而瀏覽器就會是個中繼點。但有了 CORS 機制的幫助,瀏覽器發現回覆的 Domain 和當前的不同,就會阻止 Script 接收到這些資料,從而避免了一次攻擊。
但就是因為瀏覽器的這個安全機制,我們在開發階段時總是繞不開這個常見的錯誤。
由於在前端開發時,通常會啟動一個 Dev Web Server 來 Serve 前端的 JavaScript、HTML、CSS 等靜態檔案給瀏覽器,此 Server 常與提供 API 的後端 Server 有著不同的 Domain。就算都 Host 在本機的 Localhost 上,CORS 的 Policy 規定只要 Port 不同,也不算在同個 Domain。
此時常見的做法就是在後端回覆的 Header 加上 Access-Control-Allow-Origin: *
,讓收到回覆的 Browser 知道這個 API Server 允許所有來源的請求得到回覆,這樣在前端開發時就能成功獲取的想要的資訊了。
然而在這樣設定之後,就要記得在實際要 Release 的 Production 環境關閉這樣的設定,否則就會有資安的隱患。
*區分測試環境及實際生產環境的 CORS 設定
最好的做法是在實際生產環境中的 Access-Control-Allow-Origin
設定白名單,例如 production-client.com
是我們的前端的來源 Domain,便只 Allow 此 Domain。
在設定完白名單後,Server 同時還要給予 Access-Control-Allow-Credentials: true
的 Header,讓 -client.com 的用戶在後續的 Request 中能夠附帶 -server.com 的 Cookies。