前后端分離Web項目中,RBAC實現的研究


在前后端分離Web項目中,RBAC實現的研究

 

最近手頭公司的網站項目終於漸漸走出混沌,走上正軌,任務也輕松了一些,終於有時間整理和總結一下之前做的東西。

以往的項目一般使用模板引擎(如ejs)渲染出完整頁面,再發送到瀏覽器展現。但這次項目的處理方式不同,整個項目由前端AngularJS和后端NodeJS進行了前后端的分離。后端Nodejs提供靜態文件服務和API接口,前端則通過AJAX請求調用后端的API,已JSON數據包來進行數據交換。

同時,用戶權限管理方面,我選用了RBAC(基於角色的訪問控制,Role-based access control)最基本的實現來管理(用戶表、角色表、權限表、用戶-角色關聯表、角色-權限關聯表)。而當我實際開發這個模塊的時候,立刻就意識到和以往項目的區別:

在使用后端模板引擎渲染頁面的時候,后端可以根據用戶權限有選擇地渲染一些受限的頁面元素。

而在前后端分離的開發方式下,由於后端並不渲染任何頁面,是否顯示某個頁面元素,完全是由前端來決定的。

 

對此,我考慮了一種相對簡單的方式:

將用戶最終所具有的所有權限通過API返回給前端,前端根據權限控制頁面元素。

后端本身就知道用戶權限,每個API接口的入口處添加權限的檢查。

 

然而,這樣的處理方式是有一定局限性的:

后端每個API入口處都必須寫一遍檢查。

僅僅到權限級別,太過寬泛,無法進行粒度更小的控制。

權限雖然可以通過用戶、角色來實現可配置,但是權限與頁面元素、API是否允許調用之間卻是由代碼編寫確定,這部分是無法配置的。

 

這顯然不是我能做到的最佳解決方案。

至少對於后端,我想要的是一種更加靈活可配置,並對業務代碼透明的權限檢查方式

 

於是,我在之前解決方案的基礎上,做了進一步細化,並將頁面元素、API都當作資源來看待。同時,為了前端可以做更加靈活的控制,頁面元素進一步抽象成一份Key-Value數據(我稱之為頁面環境變量ENV),供前端使用。這樣,用戶經過RBAC模塊最終得到的不是權限,而是“允許訪問的API列表(支持*和**通配)”和“環境變量”。

於是,我在權限表之后,又增加了:API路徑表、ENV環境變量表、及相關關聯表。例如:

角色-權限:

角色 權限
用戶 基礎權限
管理員 管理員基礎權限
用戶管理員 用戶管理員權限
用戶管理員首頁

權限-API路徑:

權限 API路徑 說明
基礎權限 /public/** 所有公共資源
/myAccount/** 個人賬戶所有操作
用戶管理員權限 /manage/users/do/list 后台管理用戶列表
/manage/users/*/do/get 后台管理查看任意用戶信息

權限-ENV環境變量:

權限 Key Value 說明
管理員基礎權限 ShowManageBtn 1 顯示“管理”按鈕
pageSizeForManage 50 后台管理列表頁行數
用戶管理員權限 ShowManageUsersBtn 1 后台管理顯示“用戶”按鈕
用戶管理員首頁 manageHomePageUrl /main.html#/manage/users 后台管理頁面首頁地址

 

其中,用戶最終的API路徑表用於后端API進行權限的檢查。ENV環境變量返回給前端供其控制頁面。

 

具體而言:

后端只要在請求入口(而不是單個API入口)處,對請求的URL路徑和上面的“允許訪問的API路徑列表”進行比對。請求的路徑如在列表中,則通過檢查,之后的業務邏輯無需再關心權限問題。如不在列表中,則直接返回拒絕請求。

前端則可以直接使用ENV環境變量中的Key-Value來控制頁面,

如配合AngularJS的ng-show: <div ng-show="hasEnv('ShowManageBtn')">...</div>  

通過使用這種方式,不僅避免了對每個權限進行 if...else 處理,同時,對后續修改及配置提供了相當大的便利。

 

當然,這種處理方式也有一些注意點(或者說缺點)。

1. API路徑設置要合理有規律,方便通配(單個星號通配1層路徑、兩個星號通配多層路徑)

統一路徑命名:如管理類接口都由/manage開頭,那么在配置時,只需要使用/manage/**即可通配所有的路徑(包括:/manage/home、/manage/users/list)。

做好分層規划:如管理類接口可以按功能細分 /manage/users/**、/manage/posts/**

以統一的動詞結尾:如只讀權限可以配置為/manage/**/do/get,添加權限可以配置為/manage/**/do/add。

處理對象的ID寫進路徑:如查看用戶信息的路徑為 /manage/users/1/do/get、/manage/users/2/do/get,可以配置權限為/manage/users/*/do/get

2. 避免粒度過細

過細的粒度會導致API路徑以及ENV環境變量數量的暴增。不僅管理起來麻煩,而且也會一定程度上影響權限檢查時的效率。

對於API路徑的粒度,一定要嚴格管理API路徑格式以及盡量使用通配,詳細見上述第1條。

對於ENV環境變量,可以考慮合並一些總是一起出現的內容。如系統只區分讀權限和寫權限,則添加、編輯、刪除按鈕的控制就可以合並為一個 {"btnForWrite": 1} 來處理,而不是為每個按鈕都添加一個ENV環境變量。甚至可以將Value設置為一個JSON來保存多個值 {"canWrite":{"addBtn":1, "editBtn":1}} 

3. 設置不進行RBAC認證的API路徑白名單

比如之前例子中的“基礎權限”中的 /public/** 這類肯定允許訪問的路徑,其實是沒有必要特地添加一個權限來管理的。

這類API路徑應當寫到網站配置中,作為不進行RBAC認證的API路徑。

4. 對API路徑及ENV環境變量列表進行緩存

每個請求都查詢數據庫來獲取API路徑列表顯然有點浪費資源。可以針對每個用戶,將API列表及環境變量保存到Session中,這樣只有第一次請求時才會查詢數據庫,之后的請求在RBAC檢查權限時,只需要從Session取回之前緩存的API列表和ENV環境變量即可。但要注意用戶權限在改變時,需要及時令Session中的數據失效。

我在項目中的做法是保存進Redis中,並以 cache@rbac#userId:{userId} 為Key進行保存。當用戶角色發生變化時,按照 cache@rbac#userId:{userId} 清除當前用戶的權限緩存即可;當角色、權限、API路徑、ENV環境變量發生變化時,由於不是很好明確到底影響多少用戶,所以直接按照 cache@rbac#userId:* 來清除所有用戶的權限緩存。

 

總結:前后端分離的項目中,

1. 后端服務器僅僅暴露API,所以功能級權限管理可以處理為API的訪問控制。

2. 前端頁面需要從后端服務器獲知如何控制頁面元素。同時,直接返回控制方式或具體數值可以減少前端 if...else 數量。

 

/*
 * Yiling Zhou
 * Shanghai, China
 * -.-- .. .-.. .. -. --. / --.. .... --- ..-
 * ... .... .- -. --. .... .- .. --..-- / -.-. .... .. -. .-
 */
 
 
標簽:  權限RBACAngularJSNodeJS


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM