iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 8
1
Software Development

今晚我想來點 Express 佐 MVC 分層架構系列 第 8

[今晚我想來點 Express 佐 MVC 分層架構] DAY 08 - Express CORS

阿呆最近在寫一個案子,是前後端分離的架構,前端與後端各有自己的網域,正當他開開心心要從前端 POST 一筆資料到後端時,發生了錯誤,按下 F12 看到了這個錯誤訊息:
https://ithelp.ithome.com.tw/upload/images/20200812/20119338lQuqvxcXfg.png
哪泥?!是 API 壞掉了嗎?!阿呆仔細看了一下錯誤訊息,發現了關鍵字 CORS,什麼是 CORS 呢?為什麼會導致阿呆沒辦法順利 POST 資料呢?

跨域存取

在談 CORS 的時候一定會提到跨域存取問題,所謂的跨域存取簡單來說就是 從別的網域存取資料 ,阿呆 POST 一筆資料到後端事實上就是進行了跨網域的請求。不過究竟是為什麼跨域存取會不成功呢?主要是受到 同源政策(Same-origin policy) 的影響,瀏覽器會自動將這樣的請求攔截,導致跨域存取不成功。

https://ithelp.ithome.com.tw/upload/images/20200814/201193384C1Ph2I4TP.png

同源政策

我們可以把同源政策想得很簡單,假設今天阿呆很欣賞小明家中的梵谷名畫,因為他很喜歡所以就可以不經過小明的同意借用梵谷名畫嗎?這很不合邏輯對吧!同理,A 網域向 B 網域存取資料,沒道理 B 網域一定要提供給 A 網域來使用。那我們再換個角度想,阿呆今天想跟小明借梵谷名畫,阿呆應該要先向小明 獲得許可 才能夠借走,同理,跨網域的請求也應該要被允許才可以,所以衍伸出了所謂 跨來源資源共享 (Cross-Origin Resource Sharing, CORS)

CORS

CORS 的運作方式在不同的請求下有不同的處理方式,主要會把請求分類為 簡單請求非簡單請求

簡單請求

簡單請求的符合條件如下:

  • 請求的方法為 GETHEADPOST 其中一個
  • Header 中僅限用:Accept、Accept-LanguageContent-LanguageLast-Event-IDDPRSave-DataViewport-Width、Width
  • Content-Type 的值僅接受:application/x-www-form-urlencodedmultipart/form-datatext/plain

在簡單請求下,若 A 網域的網站向 B 網域的 Server 請求資源時,瀏覽器判定為跨域存取,此時瀏覽器會在 Header 添加訊息,並發出 CORS 請求,若不在被允許範圍內的話就無法存取。

非簡單請求

在非簡單請求下, 若 A 網域的網站向 B 網域的 Server 請求資源時,瀏覽器判定此請求為跨域存取,所以會先向 B 網域的 Server 發出 OPTION 的請求,Server 收到請求時,會回傳帶有許可規則的 Header,此時瀏覽器會去判斷是否有符合此規則,若符合才會送出本來的資源請求。

https://ithelp.ithome.com.tw/upload/images/20200814/20119338bBNCOq55gr.png

Express CORS

Express 預設是不允許跨域存取的,簡單來說就是沒有做 CORS 相關設置,但官方有推出一個簡單易用的中介軟體來實作 CORS,那我們就來安裝一下吧!

npm install cors

安裝 type 定義檔:

npm install @types/cors --save-dev

安裝完成後,在應用程式層做配置,所以在 index.ts 中新增下方程式碼:

import cors from 'cors';
app.use(cors());

這樣就完成了,但不是最好的配置方式,因為 cors 預設為開好開滿,所有跨域請求都接受:

{
  "origin": "*",
  "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
  "preflightContinue": false,
  "optionsSuccessStatus": 204
}

好的作法應該是授權指定的網域、請求方法等,所以 cors 有提供一些參數可以設定:

  • origin:設置可存取的網域,也就是設置 Access-Control-Allow-Origin 參數到 Header 中。可接受的資料型別為 BooleanStringRegExpArray<String|RegExp>Function
  • methods:設置可存取的方法,也就是設置 Access-Control-Allow-Methods 參數到 Header 中。可以接受的參數格式:GET,POST,DELETE 這種用逗號隔開的,也接受 Array<String> 的格式,如:['GET', 'POST']
  • allowedHeaders:設置可存取的Header,也就是設置 Access-Control-Allow-Headers 參數到 Header 中。可以接受的參數格式:Content-Type,Authorization 這種用逗號隔開的,也接受 Array<String> 的格式,如:['Content-Type', 'Authorization']
  • exposedHeaders:設置瀏覽器可檢視的其他 Header 項目,也就是設置 Access-Control-Expose-Headers 參數到 Header 中。可以接受的參數格式:Content-Range,X-Content-Range 這種用逗號隔開的,也接受 Array<String> 的格式,如:['Content-Range', 'X-Content-Range']
  • credentials:設置是否傳送 cookie,也就是設置 Access-Control-Allow-Credentials 參數到 Header 中。資料型別:Boolean
  • maxAge:設置合法期間,使瀏覽器在這段期間內不必發送 OPTION 請求,也就是設置 Access-Control-Max-Age 參數到 Header 中。資料型別:Number 且需整數
  • preflightContinue:傳遞 OPTION 請求的 response 到下一個 handler
  • optionsSuccessStatus:設置當 OPTION 請求成功時,回傳的 HTTP Code

不過我們僅用於測試而已,所以就使用預設的配置,再透過開發人員工具進行 POST 的動作,看是否會成功:
https://ithelp.ithome.com.tw/upload/images/20200814/20119338Hz0ALDCW3F.png

小結

在這前後端分離的時代,CORS 與我們息息相關,要怎麼在合理的情況下使用 CORS 更是一門學問,相信大家在看完這篇文章後,對 CORS 有更進一步的了解,以及如何在 Express 中實作。下一篇會告訴各位,Express 要怎麼習得減傷技能,降低及限制攻擊者的攻擊手段 (中二魂上身) ,敬請期待!

參考資料

cors 官方文檔
MDN CORS


上一篇
[今晚我想來點 Express 佐 MVC 分層架構] DAY 07 - Express 錯誤處理
下一篇
[今晚我想來點 Express 佐 MVC 分層架構] DAY 09 - Express 安全防護
系列文
今晚我想來點 Express 佐 MVC 分層架構30

尚未有邦友留言

立即登入留言