實踐指南:如何在Node.js中建立一個安全的REST API
最後更新時間:2023-11-27

什麼是REST?
REST,全稱為Representational State Transfer,即「表現層狀態轉換」,是由Roy Fielding於2000年在其博士論文中提出的一種軟體架構風格。它是一組約束性原則和建議,用以指導網路系統的設計與開發。透過REST架構設計的API稱為RESTful API,這類API強調資源(如文件、圖片、服務等)的表達形式和自我描述性。
RESTful API具有以下特點: 1. 無狀態性(Stateless):每次請求都包含所有必要信息,使得伺服器不需要保存用戶上下文或會話信息。 2. 客戶端-伺服器分離(Client-Server Separation):客戶端與伺服器職責明確分開;客戶端負責使用者交互界面,而伺服器則負責數據存儲、操作和安全性。 3. 可快取(Cacheable):客戶端可以對響應內容進行快取處理以提高效能。
4. 統一介面(Uniform Interface):通過統一且受限的方法來管理資源;例如HTTP協定中常見的GET, POST, PUT, DELETE等動詞。 5. 分層系統(Layered System):可在架構之間添加多層次抽象以增強安全性或加速處理流程。 採用REST原則設計的Web API易於理解與使用,因其基於廣泛使用並被良好支持的HTTP協定。
它們往往具有高度靈活性和可拓展性,在異質系統間促進了無縫集成和互操作性。 在實作時,每個資源通常會對應到唯一URL。利用HTTP方法來執行CRUD操作——建立(Create)、讀取(Read)、更新(Update)及刪除(Delete),有效地映射了傳統數据庫操作。
例如,在Todo List 應用程序中,“todo項目”可能是一個資源。“新增todo項目”的動作可能會使用POST方法發送至/todos URL;而讀取某個特定todo項目則可能會通過GET方法向/todos/{id} 發起請求。 隨着微服務架构与前後端分离设计模式日益普及,在现代网络应用开发中构建符合REST原则的API已经变得非常重要。
这些API为各种终端用户设备如智能手机、平板电脑甚至物联网设备提供数据接入点,并支撑了复杂应用场景下数据与服务之间灵活且高效的交互方式。 在實際開發中仍需注意诸多挑战与最佳实践——比如确保接口设计具有良好扩展性与版本管理机制;合理规划资源命名策略;考量响应数据格式是否满足前后端需求,并对于大规模分布式系统还需关注负载均衡、容错处理等问题。因此,在创建一个安全且健壮的REST API时需要综合考虑到这些方面,并針對特定业务场景实施恰当策略以确保系统整体质量与长期可维护性。
RESTful API具有以下特點: 1. 無狀態性(Stateless):每次請求都包含所有必要信息,使得伺服器不需要保存用戶上下文或會話信息。 2. 客戶端-伺服器分離(Client-Server Separation):客戶端與伺服器職責明確分開;客戶端負責使用者交互界面,而伺服器則負責數據存儲、操作和安全性。 3. 可快取(Cacheable):客戶端可以對響應內容進行快取處理以提高效能。
4. 統一介面(Uniform Interface):通過統一且受限的方法來管理資源;例如HTTP協定中常見的GET, POST, PUT, DELETE等動詞。 5. 分層系統(Layered System):可在架構之間添加多層次抽象以增強安全性或加速處理流程。 採用REST原則設計的Web API易於理解與使用,因其基於廣泛使用並被良好支持的HTTP協定。
它們往往具有高度靈活性和可拓展性,在異質系統間促進了無縫集成和互操作性。 在實作時,每個資源通常會對應到唯一URL。利用HTTP方法來執行CRUD操作——建立(Create)、讀取(Read)、更新(Update)及刪除(Delete),有效地映射了傳統數据庫操作。
例如,在Todo List 應用程序中,“todo項目”可能是一個資源。“新增todo項目”的動作可能會使用POST方法發送至/todos URL;而讀取某個特定todo項目則可能會通過GET方法向/todos/{id} 發起請求。 隨着微服務架构与前後端分离设计模式日益普及,在现代网络应用开发中构建符合REST原则的API已经变得非常重要。
这些API为各种终端用户设备如智能手机、平板电脑甚至物联网设备提供数据接入点,并支撑了复杂应用场景下数据与服务之间灵活且高效的交互方式。 在實際開發中仍需注意诸多挑战与最佳实践——比如确保接口设计具有良好扩展性与版本管理机制;合理规划资源命名策略;考量响应数据格式是否满足前后端需求,并对于大规模分布式系统还需关注负载均衡、容错处理等问题。因此,在创建一个安全且健壮的REST API时需要综合考虑到这些方面,并針對特定业务场景实施恰当策略以确保系统整体质量与长期可维护性。
優勢 | 劣勢 | |
---|---|---|
機會 |
|
|
威脅 |
|
|
表: 強弱危機分析(最後更新: 2023-11-27)
GET方法
這段文字可重編為:「此動作從指定的URI擷取資源的表現形式。回應訊息的內容提供我們所要求之資源的詳細資訊。」
POST方法
這個操作在目標URI上創建一個新的資源。請求訊息的主體提供了新資源的詳細內容。值得注意的是,POST方法也可以觸發不創建資源的操作。
PUT方法
PUT方法負責在目標URI上創建或替換資源。請求訊息的主體指定了要更新或創建的資源。這個動作就像是人類語言中描述的一樣,讓閱讀更加流暢、易於理解。
PATCH方法
「PATCH」是用來對資源進行部分更新的動作。請求主體內容包含了要應用於該資源上的一系列變更。這些修改項目能夠使文章更加易讀、流暢,猶如人類之間的交流方式。
DELETE方法
DELETE指令的作用是在目標URI上刪除資源。透過使用DELETE,我們可以將特定URI上的資源丟棄或移除。
REST的原則
正如之前討論的,Fielding博士是首位提出REST一詞的人。他還提出了以下六個REST的指導原則。
無狀態性
客戶端向伺服器發送的請求包含了所有必要的資訊,以便完全理解該請求。這些請求可以是查詢字串參數、主體、URI,甚至是標頭。主體負責保存請求資源的狀態,而URI則用於識別該資源。
在伺服器完成處理後,會通過狀態碼、標頭或回應主體向客戶端返回適當的回應。
在伺服器完成處理後,會通過狀態碼、標頭或回應主體向客戶端返回適當的回應。
客戶端-伺服器架構
客戶端-伺服器模型包含一個統一界面,作為隔離客戶端和伺服器之間的屏障。這種分離改善了用戶界面在多個平台上的可移植性,同時提高了伺服器元件的可擴展性。
重點改寫:
客戶端-伺服器模型有一個統一界面,像是將客戶端和伺服器分開的屏障。
這種分離讓用戶界面更容易在不同平台上使用,也能夠增加伺服器元件的可擴展性。
這種分離讓用戶界面更容易在不同平台上使用,也能夠增加伺服器元件的可擴展性。
統一介面原則
REST 定義了以下四個介面限制來確保應用程式的統一性。這些限制有助於使資源識別、使用表示來操作資源、自我描述訊息以及超媒體作為應用程式狀態引擎等內容更加易於理解和順暢。
首先,資源識別是 REST 中重要的一個方面。
它通過唯一的 URI(Uniform Resource Identifier)來識別每個資源,確保每個資源都具有獨特的身份。 其次,REST 強調使用表示來操作資源。這意味著客戶端與服務器之間的互動主要是基於對資源表現形式的操作。
例如,客戶端可以使用 GET 方法獲取資源的表示形式,使用 POST 方法創建新的資源,使用 PUT 方法更新現有的資源等。 另外,自我描述訊息也是 REST 的核心原則之一。這意味著每個訊息都攜帶足夠的資訊以讓接收者理解該訊息,而無需額外參考其他上下文或文件。
最後,超媒體作為應用程式狀態引擎是 REST 的一個重要特點。這意味著服務器在回應中將提供與資源相關的超媒體連結,客戶端可以根據這些連結來導航和操作其他相關的資源。 總而言之,REST 通過定義這四個介面限制來確保應用程式在設計和實現上具有統一性。
這些限制使得資源識別、表示形式操作、自我描述訊息和超媒體作為應用程式狀態引擎等內容更加易於理解和順暢。
它通過唯一的 URI(Uniform Resource Identifier)來識別每個資源,確保每個資源都具有獨特的身份。 其次,REST 強調使用表示來操作資源。這意味著客戶端與服務器之間的互動主要是基於對資源表現形式的操作。
例如,客戶端可以使用 GET 方法獲取資源的表示形式,使用 POST 方法創建新的資源,使用 PUT 方法更新現有的資源等。 另外,自我描述訊息也是 REST 的核心原則之一。這意味著每個訊息都攜帶足夠的資訊以讓接收者理解該訊息,而無需額外參考其他上下文或文件。
最後,超媒體作為應用程式狀態引擎是 REST 的一個重要特點。這意味著服務器在回應中將提供與資源相關的超媒體連結,客戶端可以根據這些連結來導航和操作其他相關的資源。 總而言之,REST 通過定義這四個介面限制來確保應用程式在設計和實現上具有統一性。
這些限制使得資源識別、表示形式操作、自我描述訊息和超媒體作為應用程式狀態引擎等內容更加易於理解和順暢。
可快取處理原則
應用程式可被快取以提供更好的效能。這是通過將服務器的回應標記為可快取或不可快取,無論是隱含還是明確地來實現的。如果回應被定義為可快取,則客戶端快取可以在未來重複使用所有相等的回應數據。
此外,它還有助於避免重複使用陳舊的數據。
此外,它還有助於避免重複使用陳舊的數據。
分層系統原則
分層系統架構是一種設計方法,它對組件行為進行限制以確保應用程序更加穩定。同時,這種架構還提供共享緩存功能,從而促進了系統的可伸縮性。此外,該架構還實現了負載平衡功能。
值得一提的是,分層架構能夠增強應用程序的安全性,因為各個層次之間的組件只能與本層中的其他組件交互作用。
值得一提的是,分層架構能夠增強應用程序的安全性,因為各個層次之間的組件只能與本層中的其他組件交互作用。
需求即時程式碼原則
設置流程說明
首先,你必須確保你擁有最新的Node.js版本。在這裡,我將使用從nodejs.org下載的8.11.2版本。接下來要確保你已經安裝了MongoDB。
如果還沒有安裝,可以從www.mongodb.com上進行安裝。然後創建一個名為example-rest-api的文件夾,我們將在這個文件夾中進行項目開發。命名完後,在該檔夾中打開終端機或git CLI控制台並運行npm init命令創建package.json文件。
此外,我們還會在這個項目中使用Express框架。
如果還沒有安裝,可以從www.mongodb.com上進行安裝。然後創建一個名為example-rest-api的文件夾,我們將在這個文件夾中進行項目開發。命名完後,在該檔夾中打開終端機或git CLI控制台並運行npm init命令創建package.json文件。
此外,我們還會在這個項目中使用Express框架。
建立帳號模塊說明
為了建立帳戶模組,我們將使用Mongoose。它是一個物件數據建模的ODM(Object Data Modeling),可在帳戶架構中創建用戶模型。首先,在/account/models/account.model.js中,我們必須創建架構:一旦定義了用戶模型的架構,將其附加到用戶模型變得很容易。
const accountModel = mongoose.model(′Account′,accountSchema);完成這一步後,我們可以使用它來實現所有CRUD操作。讓我們從“創建帳戶”操作開始,在account/routes.config.js中定義路由。此時,我們可以通過運行服務器並向/account發送POST請求以及一些JSON數據來驗證我們的Mongoose模型。
我們還可以通過/accounts/controllers/account.controller.js中的控制器恰當地對密碼進行雜湊處理。 我們知道Post API可用於添加新帳戶請求。在這種情況下, 它創建了帳戶介面. 首先, joi()庫會開始驗證檢查. 它會驗證請求來源是否經授權. 如果請求來自未經授權使用者, 則會發送錯誤. 而如果請求來自經過授權的用戶, 則會將記錄添加到資料庫中. 任務成功完成後, 它將發送一個包含帳戶物件的201狀態碼. 這個帳戶將在以下控制器中實現為GET請求: ′按ID獲取′,顧名思義,此操作用於調取任何ID的記錄。
上述端點根據所請求的帳戶ID返回回應。它首先檢查請求的帳戶ID是否存在於資料庫中。如果不存在,則會在回應中發送“未找到”錯誤。
如果帳戶ID存在,則會發送狀態碼200以及單個帳戶物件。帳戶清單將通過以下控制器以GET方式實現:/accounts/. 最後要實現的部分是DELETE /accounts/:accountId. 我們的刪除控制器如下:顧名思義,Delete API用於刪除記錄。在這種情況下, 它首先檢查請求許可權. 如果發現請求已經被授權, 則成功從資料庫中刪除記錄並返回204狀態碼作為回應. 現在我們擁有了處理用戶資源所需的所有操作. 此外, 我們不需要使用者控制器進行進一步處理. 此代碼的主要目標是向您介紹使用REST模式的核心概念. 對於某些許可權和驗證方面的實施, 我們需要回到這段代碼中來完成它們. 但在此之前, 我們需要建立自己的安全性. 因此,讓我們創建auth模組。
const accountModel = mongoose.model(′Account′,accountSchema);完成這一步後,我們可以使用它來實現所有CRUD操作。讓我們從“創建帳戶”操作開始,在account/routes.config.js中定義路由。此時,我們可以通過運行服務器並向/account發送POST請求以及一些JSON數據來驗證我們的Mongoose模型。
我們還可以通過/accounts/controllers/account.controller.js中的控制器恰當地對密碼進行雜湊處理。 我們知道Post API可用於添加新帳戶請求。在這種情況下, 它創建了帳戶介面. 首先, joi()庫會開始驗證檢查. 它會驗證請求來源是否經授權. 如果請求來自未經授權使用者, 則會發送錯誤. 而如果請求來自經過授權的用戶, 則會將記錄添加到資料庫中. 任務成功完成後, 它將發送一個包含帳戶物件的201狀態碼. 這個帳戶將在以下控制器中實現為GET請求: ′按ID獲取′,顧名思義,此操作用於調取任何ID的記錄。
上述端點根據所請求的帳戶ID返回回應。它首先檢查請求的帳戶ID是否存在於資料庫中。如果不存在,則會在回應中發送“未找到”錯誤。
如果帳戶ID存在,則會發送狀態碼200以及單個帳戶物件。帳戶清單將通過以下控制器以GET方式實現:/accounts/. 最後要實現的部分是DELETE /accounts/:accountId. 我們的刪除控制器如下:顧名思義,Delete API用於刪除記錄。在這種情況下, 它首先檢查請求許可權. 如果發現請求已經被授權, 則成功從資料庫中刪除記錄並返回204狀態碼作為回應. 現在我們擁有了處理用戶資源所需的所有操作. 此外, 我們不需要使用者控制器進行進一步處理. 此代碼的主要目標是向您介紹使用REST模式的核心概念. 對於某些許可權和驗證方面的實施, 我們需要回到這段代碼中來完成它們. 但在此之前, 我們需要建立自己的安全性. 因此,讓我們創建auth模組。
建立驗證模塊說明
在開始保護帳戶模組之前,我們需要為當前帳戶生成有效的權杖。這可以通過驗證和權限的實施來完成。這裡我們將根據用戶提供的電子郵件和密碼生成一個JWT(JSON Web Token)。
JWT是一種優秀的JSON Web權杖,可以安全地用於大量請求而無需重複驗證。首先,我們會創建一個用於POST請求到/auth資源的端點。請求體將包含密碼和用戶電子郵件。
在調用控制器之前,我們應該在/authorization/middlewares/verify.user.middleware.js中驗證用戶。現在,我們所需的就是在/authorization/routes.config.js中調用相應的中間件:同時創建路由。響應將包括在accessToken欄位中生成的JWT:權杖創建後,我們可以將其放入Authorization頭部使用Bearer ACCESS_TOKEN形式使用它。
JWT是一種優秀的JSON Web權杖,可以安全地用於大量請求而無需重複驗證。首先,我們會創建一個用於POST請求到/auth資源的端點。請求體將包含密碼和用戶電子郵件。
在調用控制器之前,我們應該在/authorization/middlewares/verify.user.middleware.js中驗證用戶。現在,我們所需的就是在/authorization/routes.config.js中調用相應的中間件:同時創建路由。響應將包括在accessToken欄位中生成的JWT:權杖創建後,我們可以將其放入Authorization頭部使用Bearer ACCESS_TOKEN形式使用它。
建立權限和驗證中間件說明
首先,我們需要定義誰可以使用用戶資源。我們需要處理以下情境:
1. 公開場景:用於註冊過程(創建用戶)。在這種情況下,我們不會使用JWT。
2. 私人場景:供管理員更新用戶資訊以及已登錄的用戶使用。只有管理員才能執行此操作。 3. 移除用戶帳號的私人場景:僅限管理員執行。
經確定這些情境後,我們將需要一個中間件來驗證使用者是否具有有效的JWT。 中間件位於/common/middlewares/auth.validation.middleware.js。對於處理請求錯誤,我們將使用HTTP錯誤代碼: 1. 無效請求時使用HTTP 401。
2. 使用有效權杖但權限無效時使用HTTP 403。 這裡的中間件是通用的。如果所需權限級別和使用者權限級別至少有一位元相同,那麼結果將大於零,因此可以允許該操作進行。
否則,返回HTTP 403。
2. 私人場景:供管理員更新用戶資訊以及已登錄的用戶使用。只有管理員才能執行此操作。 3. 移除用戶帳號的私人場景:僅限管理員執行。
經確定這些情境後,我們將需要一個中間件來驗證使用者是否具有有效的JWT。 中間件位於/common/middlewares/auth.validation.middleware.js。對於處理請求錯誤,我們將使用HTTP錯誤代碼: 1. 無效請求時使用HTTP 401。
2. 使用有效權杖但權限無效時使用HTTP 403。 這裡的中間件是通用的。如果所需權限級別和使用者權限級別至少有一位元相同,那麼結果將大於零,因此可以允許該操作進行。
否則,返回HTTP 403。
如何運行並用JEST進行測試
Jest是一個不錯的JavaScript測試框架,主要注重簡單易用。它可以與TypeScript、Node、Angular、Vue、Babel、React等項目一起使用。Jest在確保測試具有唯一的全域狀態後,並行運行測試以提高可靠性。
通過先運行之前失敗的測試,然後重新組織運行順序來加快速度。現在,要創建一個帳戶,我們需要根據相應的端點POST所有必需的欄位,並隨後存儲生成的ID。此後,API將回覆帳戶ID:現在可以使用/auth/endpoint生成JWT:我們應該收到權杖作為響應:將accessToken添加到請求標頭下Authorization並加上前綴Bearer,在實施權限中間件後,如果我們現在不這樣做,除了註冊之外的所有請求都將返回HTTP代碼401.只要存在有效權杖,我們就會從/accounts/:accountId接收以下響應:僅供簡單和教育目的顯示所有欄位時提醒一下, 密碼不應該在響應中顯示。
讓我們獲取帳戶列表:我們得到一個403的響應。這意味著我們的帳戶沒有訪問此端點的權限。因此,我們需要手動將帳戶的permissionLevel從1更改為7在MongoDB中。
之後,我們可以生成新的JWT。完成後,我們收到正確的響應:接下來,我們將通過向/accounts/:accountId端點發送PATCH請求以及一些欄位來測試更新功能:在這裡,我們期望收到204作為成功操作的確認回覆。但是,我們可以再次請求帳戶以進行驗證。
最後,我們需要刪除該帳戶。為此,我們需要按照上述代碼創建一個新帳戶(不要忘記記下帳戶ID)。同時,還需要確保具有管理員帳號所需的適當JWT. 最後, 我們發送DELETE請求給/accounts/: accountId. 我們必須收到204作為確認回復. 可以通過請求/accounts/來驗證它是否已被刪除了所有現有用戶.
通過先運行之前失敗的測試,然後重新組織運行順序來加快速度。現在,要創建一個帳戶,我們需要根據相應的端點POST所有必需的欄位,並隨後存儲生成的ID。此後,API將回覆帳戶ID:現在可以使用/auth/endpoint生成JWT:我們應該收到權杖作為響應:將accessToken添加到請求標頭下Authorization並加上前綴Bearer,在實施權限中間件後,如果我們現在不這樣做,除了註冊之外的所有請求都將返回HTTP代碼401.只要存在有效權杖,我們就會從/accounts/:accountId接收以下響應:僅供簡單和教育目的顯示所有欄位時提醒一下, 密碼不應該在響應中顯示。
讓我們獲取帳戶列表:我們得到一個403的響應。這意味著我們的帳戶沒有訪問此端點的權限。因此,我們需要手動將帳戶的permissionLevel從1更改為7在MongoDB中。
之後,我們可以生成新的JWT。完成後,我們收到正確的響應:接下來,我們將通過向/accounts/:accountId端點發送PATCH請求以及一些欄位來測試更新功能:在這裡,我們期望收到204作為成功操作的確認回覆。但是,我們可以再次請求帳戶以進行驗證。
最後,我們需要刪除該帳戶。為此,我們需要按照上述代碼創建一個新帳戶(不要忘記記下帳戶ID)。同時,還需要確保具有管理員帳號所需的適當JWT. 最後, 我們發送DELETE請求給/accounts/: accountId. 我們必須收到204作為確認回復. 可以通過請求/accounts/來驗證它是否已被刪除了所有現有用戶.
留言