设计一个权限系统-RBAC
系統(tǒng)安全一直是在系統(tǒng)開(kāi)發(fā)中不可規(guī)避的問(wèn)題,而權(quán)限控制又跟系統(tǒng)安全密不可分,大到用戶的訪問(wèn),小到一個(gè)頁(yè)面的按鈕,都有可能涉及到權(quán)限的控制。
RBAC權(quán)限模型
迄今為止最為普及的權(quán)限設(shè)計(jì)模型是RBAC模型,基于角色的訪問(wèn)控制(Role-Based Access Control)。
1、RBAC0模型
簡(jiǎn)單地說(shuō),一個(gè)用戶擁有若干角色,每一個(gè)角色擁有若干權(quán)限。這樣,就構(gòu)造成“用戶-角色-權(quán)限”的授權(quán)模型。
RBAC0模型如下:
這是權(quán)限最基礎(chǔ)也是最核心的模型,它包括用戶/角色/權(quán)限,其中用戶和角色是多對(duì)多的關(guān)系,角色和權(quán)限也是多對(duì)多的關(guān)系。
用戶是發(fā)起操作的主體,按類型分可分為2B和2C用戶,可以是后臺(tái)管理系統(tǒng)的用戶,可以是OA系統(tǒng)的內(nèi)部員工,也可以是面向C端的用戶,比如阿里云的用戶。
角色起到了橋梁的作用,連接了用戶和權(quán)限的關(guān)系,每個(gè)角色可以關(guān)聯(lián)多個(gè)權(quán)限,同時(shí)一個(gè)用戶關(guān)聯(lián)多個(gè)角色,那么這個(gè)用戶就有了多個(gè)角色的多個(gè)權(quán)限。有人會(huì)問(wèn)了為什么用戶不直接關(guān)聯(lián)權(quán)限呢?
在用戶基數(shù)小的系統(tǒng),比如20個(gè)人的小系統(tǒng),管理員可以直接把用戶和權(quán)限關(guān)聯(lián),工作量并不大,選擇一個(gè)用戶勾選下需要的權(quán)限就完事了。
但是在實(shí)際企業(yè)系統(tǒng)中,用戶基數(shù)比較大,其中很多人的權(quán)限都是一樣的,就是個(gè)普通訪問(wèn)權(quán)限,如果管理員給100人甚至更多授權(quán),工作量巨大。
這就引入了"角色(Role)"概念,一個(gè)角色可以與多個(gè)用戶關(guān)聯(lián),管理員只需要把該角色賦予用戶,那么用戶就有了該角色下的所有權(quán)限,這樣設(shè)計(jì)既提升了效率,也有很大的拓展性。
一句話概括就是:權(quán)限系統(tǒng)維護(hù)的是人和可進(jìn)行行為的關(guān)聯(lián)關(guān)系。
那么系統(tǒng)的每個(gè)用戶可以看做是一堆可以進(jìn)行的行為的集合。
這句話有點(diǎn)不好理解的話,你就按照用戶畫像那么理解,在權(quán)限系統(tǒng)里,每個(gè)用戶身上打滿了一堆可以進(jìn)行行為的標(biāo)簽。
權(quán)限是用戶可以訪問(wèn)的資源,包括頁(yè)面權(quán)限、操作權(quán)限、數(shù)據(jù)權(quán)限。
頁(yè)面權(quán)限:即用戶登錄系統(tǒng)可以看到的頁(yè)面,由菜單來(lái)控制,菜單包括一級(jí)菜單和二級(jí)菜單,只要用戶有一級(jí)和二級(jí)菜單的權(quán)限,那么用戶就可以訪問(wèn)頁(yè)面
操作權(quán)限: 即頁(yè)面的功能按鈕,包括查看、新增、修改、刪除、審核等,用戶點(diǎn)擊刪除按鈕時(shí),后臺(tái)會(huì)校驗(yàn)用戶角色下的所有權(quán)限是否包含該刪除權(quán)限,如果是,就可以進(jìn)行下一步操作,反之提示無(wú)權(quán)限。有的系統(tǒng)要求"可見(jiàn)即可操作",意思是如果頁(yè)面上能夠看到操作按鈕,那么用戶就可以操作,要實(shí)現(xiàn)此需求,這里就需要前端來(lái)配合,前端開(kāi)發(fā)把用戶的權(quán)限信息緩存,在頁(yè)面判斷用戶是否包含此權(quán)限,如果有,就顯示該按鈕,如果沒(méi)有,就隱藏該按鈕。某種程度上提升了用戶體驗(yàn),但是在實(shí)際場(chǎng)景可自行選擇是否需要這樣做。
數(shù)據(jù)權(quán)限:數(shù)據(jù)權(quán)限就是用戶在同一頁(yè)面看到的數(shù)據(jù)是不同的,比如財(cái)務(wù)部只能看到其部門下的用戶數(shù)據(jù),采購(gòu)部只看采購(gòu)部的數(shù)據(jù),在一些大型的公司,全國(guó)有很多城市和分公司,比如杭州用戶登錄系統(tǒng)只能看到杭州的數(shù)據(jù),上海用戶只能看到上海的數(shù)據(jù),解決方案一般是把數(shù)據(jù)和具體的組織架構(gòu)關(guān)聯(lián)起來(lái),舉個(gè)例子,再給用戶授權(quán)的時(shí)候,用戶選擇某個(gè)角色同時(shí)綁定組織如財(cái)務(wù)部或者合肥分公司,那么該用戶就有了該角色下財(cái)務(wù)部或合肥分公司下的的數(shù)據(jù)權(quán)限。
以上是RBAC的核心設(shè)計(jì)及模型分析,此模型也叫做RBAC0,而基于核心概念之上,RBAC還提供了擴(kuò)展模式:包括RBAC1、RBAC2、RBAC3模型。
下面介紹這三種類型
2、RBAC1模型
此模型引入了角色繼承(Hierarchical Role)概念,即角色具有上下級(jí)的關(guān)系,角色間的繼承關(guān)系可分為一般繼承關(guān)系和受限繼承關(guān)系。
一般繼承關(guān)系僅要求角色繼承關(guān)系是一個(gè)絕對(duì)偏序關(guān)系,允許角色間的多繼承。而受限繼承關(guān)系則進(jìn)一步要求角色繼承關(guān)系是一個(gè)樹(shù)結(jié)構(gòu),實(shí)現(xiàn)角色間的單繼承。這種設(shè)計(jì)可以給角色分組和分層,一定程度簡(jiǎn)化了權(quán)限管理工作。
3、 RBAC2模型
基于核心模型的基礎(chǔ)上,進(jìn)行了角色的約束控制,RBAC2模型中添加了責(zé)任分離關(guān)系,其規(guī)定了權(quán)限被賦予角色時(shí),或角色被賦予用戶時(shí),以及當(dāng)用戶在某一時(shí)刻激活一個(gè)角色時(shí)所應(yīng)遵循的強(qiáng)制性規(guī)則。責(zé)任分離包括靜態(tài)責(zé)任分離和動(dòng)態(tài)責(zé)任分離。
主要包括以下約束:
互斥角色:同一用戶只能分配到一組互斥角色集合中至多一個(gè)角色,支持責(zé)任分離的原則。互斥角色是指各自權(quán)限互相制約的兩個(gè)角色。比如財(cái)務(wù)部有會(huì)計(jì)和審核員兩個(gè)角色,他們是互斥角色,那么用戶不能同時(shí)擁有這兩個(gè)角色,體現(xiàn)了職責(zé)分離原則。
基數(shù)約束:一個(gè)角色被分配的用戶數(shù)量受限;一個(gè)用戶可擁有的角色數(shù)目受限;同樣一個(gè)角色對(duì)應(yīng)的訪問(wèn)權(quán)限數(shù)目也應(yīng)受限,以控制高級(jí)權(quán)限在系統(tǒng)中的分配
先決條件角色: 即用戶想獲得某上級(jí)角色,必須先獲得其下一級(jí)的角色。
4、RBAC3模型
即最全面的權(quán)限管理,它是基于RBAC0,將RBAC1和RBAC2進(jìn)行了整合
5、用戶組
當(dāng)平臺(tái)用戶基數(shù)增大,角色類型增多時(shí),而且有一部分人具有相同的屬性,比如財(cái)務(wù)部的所有員工,如果直接給用戶分配角色,管理員的工作量就會(huì)很大,如果把相同屬性的用戶歸類到某用戶組,那么管理員直接給用戶組分配角色,用戶組里的每個(gè)用戶即可擁有該角色,以后其他用戶加入用戶組后,即可自動(dòng)獲取用戶組的所有角色,退出用戶組,同時(shí)也撤銷了用戶組下的角色,無(wú)須管理員手動(dòng)管理角色。
根據(jù)用戶組是否有上下級(jí)關(guān)系,可以分為有上下級(jí)的用戶組和普通用戶組。
具有上下級(jí)關(guān)系的用戶組:最典型的例子就是部門和職位,可能多數(shù)人沒(méi)有把部門職位和用戶組關(guān)聯(lián)起來(lái)吧。當(dāng)然用戶組是可以拓展的,部門和職位常用于內(nèi)部的管理系統(tǒng),如果是面向C端的系統(tǒng),比如淘寶網(wǎng)的商家,商家自身也有一套組織架構(gòu),比如采購(gòu)部,銷售部,客服部,后勤部等,有些人擁有客服權(quán)限,有些人擁有上架權(quán)限等等,所以用戶組是可以拓展的。
普通用戶組:即沒(méi)有上下級(jí)關(guān)系,和組織架構(gòu),職位都沒(méi)有關(guān)系,也就是說(shuō)可以跨部門,跨職位,舉個(gè)例子,某電商后臺(tái)管理系統(tǒng),有拼團(tuán)活動(dòng)管理角色,我們可以設(shè)置一個(gè)拼團(tuán)用戶組,該組可以包括研發(fā)部的后臺(tái)開(kāi)發(fā)人員,運(yùn)營(yíng)部的運(yùn)營(yíng)人員,采購(gòu)部的人員等等。
每個(gè)公司都會(huì)涉及到到組織和職位,下面就重點(diǎn)介紹這兩個(gè)。
5.1 組織
常見(jiàn)的組織架構(gòu)如下圖:
我們可以把組織與角色進(jìn)行關(guān)聯(lián),用戶加入組織后,就會(huì)自動(dòng)獲得該組織的全部角色,無(wú)須管理員手動(dòng)授予,大大減少工作量,同時(shí)用戶在調(diào)崗時(shí),只需調(diào)整組織,角色即可批量調(diào)整。
組織的另外一個(gè)作用是控制數(shù)據(jù)權(quán)限,把角色關(guān)聯(lián)到組織,那么該角色只能看到該組織下的數(shù)據(jù)權(quán)限。
5.2 職位
假設(shè)財(cái)務(wù)部的職位如下圖:
每個(gè)組織部門下都會(huì)有多個(gè)職位,比如財(cái)務(wù)部有總監(jiān),會(huì)計(jì),出納等職位,雖然都在同一部門,但是每個(gè)職位的權(quán)限是不同的,職位高的擁有更多的權(quán)限。總監(jiān)擁有所有權(quán)限,會(huì)計(jì)和出納擁有部分權(quán)限。特殊情況下,一個(gè)人可能身兼多職。
6、含有組織/職位/用戶組的模型
根據(jù)以上場(chǎng)景,新的權(quán)限模型就可以設(shè)計(jì)出來(lái)了,如下圖:
根據(jù)系統(tǒng)的復(fù)雜度不同,其中的多對(duì)多關(guān)系和一對(duì)一關(guān)系可能會(huì)有變化。
在單系統(tǒng)且用戶類型單一的情況下,用戶和組織是一對(duì)一關(guān)系,組織和職位是一對(duì)多關(guān)系,用戶和職位是一對(duì)一關(guān)系,組織和角色是一對(duì)一關(guān)系,職位和角色是一對(duì)一關(guān)系,用戶和用戶組是多對(duì)對(duì)關(guān)系,用戶組和角色是一對(duì)一關(guān)系,當(dāng)然這些關(guān)系也可以根據(jù)具體業(yè)務(wù)進(jìn)行調(diào)整。模型設(shè)計(jì)并不是死的,如果小系統(tǒng)不需要用戶組,這塊是可以去掉的。
分布式系統(tǒng)且用戶類型單一的情況下,到這里權(quán)限系統(tǒng)就會(huì)變得很復(fù)雜,這里就要引入了一個(gè)"系統(tǒng)"概念,此時(shí)系統(tǒng)架構(gòu)是個(gè)分布式系統(tǒng),權(quán)限系統(tǒng)獨(dú)立出來(lái),負(fù)責(zé)所有的系統(tǒng)的權(quán)限控制,其他業(yè)務(wù)系統(tǒng)比如商品中心,訂單中心,用戶中心,每個(gè)系統(tǒng)都有自己的角色和權(quán)限,那么權(quán)限系統(tǒng)就可以配置其他系統(tǒng)的角色和權(quán)限。
# 授權(quán)流程
授權(quán)即給用戶授予角色,按流程可分為手動(dòng)授權(quán)和審批授權(quán)。權(quán)限中心可同時(shí)配置這兩種,可提高授權(quán)的靈活性。
手動(dòng)授權(quán):管理員登錄權(quán)限中心為用戶授權(quán),根據(jù)在哪個(gè)頁(yè)面授權(quán)分為兩種方式:給用戶添加角色,給角色添加用戶。
給用戶添加角色就是在用戶管理頁(yè)面,點(diǎn)擊某個(gè)用戶去授予角色,可以一次為用戶添加多個(gè)角色;給角色添加用戶就是在角色管理頁(yè)面,點(diǎn)擊某個(gè)角色,選擇多個(gè)用戶,實(shí)現(xiàn)了給批量用戶授予角色的目的。
審批授權(quán):即用戶申請(qǐng)某個(gè)職位角色,那么用戶通過(guò)OA流程申請(qǐng)?jiān)摻巧?#xff0c;然后由上級(jí)審批,該用戶即可擁有該角色,不需要系統(tǒng)管理員手動(dòng)授予。
# 表結(jié)構(gòu)
有了上述的權(quán)限模型,設(shè)計(jì)表結(jié)構(gòu)就不難了,下面是多系統(tǒng)下的表結(jié)構(gòu),簡(jiǎn)單設(shè)計(jì)下,主要提供思路。
# 權(quán)限框架
RBAC模型5大屬性,分別是:
1 用戶屬性(張三、李四、王五)
2 角色屬性(銷售經(jīng)理、銷售、前臺(tái))
3 用戶與角色的關(guān)系(張三 是 銷售經(jīng)理 、李四 王五 是 銷售)
4 權(quán)限(添加客戶、編輯客戶、刪除客戶,查看客戶)
5 權(quán)限與角色的關(guān)系(銷售 擁有 查看客戶的 權(quán) 限、銷售經(jīng)理可以 查看/添加/刪除/編輯客戶的)
一個(gè)RBAC權(quán)限模塊,必然要實(shí)現(xiàn)三個(gè)功能
用戶管理
用戶列表
添加用戶
編輯用戶
設(shè)置用戶角色
角色管理 角色列表
添加角色
編輯角色
設(shè)置角色權(quán)限
權(quán)限管理
權(quán)限列表
新增權(quán)限
編輯權(quán)限
數(shù)據(jù)表設(shè)計(jì)
用戶表
?
CREATE TABLE `user` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL DEFAULT '' COMMENT '姓名',`email` varchar(30) NOT NULL DEFAULT '' COMMENT '郵箱',`is_admin` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否是超級(jí)管理員 1表示是 0 表示不是',`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '狀態(tài) 1:有效 0:無(wú)效',`updated_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '最后一次更新時(shí)間',`created_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '插入時(shí)間',PRIMARY KEY (`id`),KEY `idx_email` (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶表';角色表
?
CREATE TABLE `role` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT,`name` varchar(50) NOT NULL DEFAULT '' COMMENT '角色名稱',`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '狀態(tài) 1:有效 0:無(wú)效',`updated_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '最后一次更新時(shí)間',`created_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '插入時(shí)間',PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色表';用戶角色表
?
CREATE TABLE `user_role` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT,`uid` int(11) NOT NULL DEFAULT '0' COMMENT '用戶id',`role_id` int(11) NOT NULL DEFAULT '0' COMMENT '角色I(xiàn)D',`created_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '插入時(shí)間',PRIMARY KEY (`id`),KEY `idx_uid` (`uid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶角色表';權(quán)限詳情表
?
CREATE TABLE `access` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT,`title` varchar(50) NOT NULL DEFAULT '' COMMENT '權(quán)限名稱',`urls` varchar(1000) NOT NULL DEFAULT '' COMMENT 'json 數(shù)組',`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '狀態(tài) 1:有效 0:無(wú)效',`updated_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '最后一次更新時(shí)間',`created_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '插入時(shí)間',PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='權(quán)限詳情表';角色權(quán)限表
?
CREATE TABLE `role_access` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT,`role_id` int(11) NOT NULL DEFAULT '0' COMMENT '角色id',`access_id` int(11) NOT NULL DEFAULT '0' COMMENT '權(quán)限id',`created_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '插入時(shí)間',PRIMARY KEY (`id`),KEY `idx_role_id` (`role_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色權(quán)限表';用戶操作記錄表
?
CREATE TABLE `app_access_log` (`id` int(11) NOT NULL AUTO_INCREMENT,`uid` bigint(20) NOT NULL DEFAULT '0' COMMENT '品牌UID',`target_url` varchar(255) NOT NULL DEFAULT '' COMMENT '訪問(wèn)的url',`query_params` longtext NOT NULL COMMENT 'get和post參數(shù)',`ua` varchar(255) NOT NULL DEFAULT '' COMMENT '訪問(wèn)ua',`ip` varchar(32) NOT NULL DEFAULT '' COMMENT '訪問(wèn)ip',`note` varchar(1000) NOT NULL DEFAULT '' COMMENT 'json格式備注字段',`created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`),KEY `idx_uid` (`uid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶操作記錄表';代碼實(shí)現(xiàn)
本系統(tǒng)所有頁(yè)面都是需要登錄之后才能訪問(wèn)的, 在框架中加入統(tǒng)一驗(yàn)證方法
?
public function beforeAction($action) {$login_status = $this->checkLoginStatus();if ( !$login_status && !in_array( $action->uniqueId,$this->allowAllAction ) ) {if(Yii::$app->request->isAjax){$this->renderJSON([],"未登錄,請(qǐng)返回用戶中心",-302);}else{$this->redirect( UrlService::buildUrl("/user/login") );//返回到登錄頁(yè)面}return false;}//保存所有的訪問(wèn)到數(shù)據(jù)庫(kù)當(dāng)中$get_params = $this->get( null );$post_params = $this->post( null );$model_log = new AppAccessLog();$model_log->uid = $this->current_user?$this->current_user['id']:0;$model_log->target_url = isset( $_SERVER['REQUEST_URI'] )?$_SERVER['REQUEST_URI']:'';$model_log->query_params = json_encode( array_merge( $post_params,$get_params ) );$model_log->ua = isset( $_SERVER['HTTP_USER_AGENT'] )?$_SERVER['HTTP_USER_AGENT']:'';$model_log->ip = isset( $_SERVER['REMOTE_ADDR'] )?$_SERVER['REMOTE_ADDR']:'';$model_log->created_time = date("Y-m-d H:i:s");$model_log->save( 0 );/*** 判斷權(quán)限的邏輯是* 取出當(dāng)前登錄用戶的所屬角色,* 在通過(guò)角色 取出 所屬 權(quán)限關(guān)系* 在權(quán)限表中取出所有的權(quán)限鏈接* 判斷當(dāng)前訪問(wèn)的鏈接 是否在 所擁有的權(quán)限列表中*///判斷當(dāng)前訪問(wèn)的鏈接 是否在 所擁有的權(quán)限列表中if( !$this->checkPrivilege( $action->getUniqueId() ) ){$this->redirect( UrlService::buildUrl( "/error/forbidden" ) );return false;}return true; }檢查是否有訪問(wèn)指定鏈接的權(quán)限
?
public function checkPrivilege( $url ){//如果是超級(jí)管理員 也不需要權(quán)限判斷if( $this->current_user && $this->current_user['is_admin'] ){return true;}//有一些頁(yè)面是不需要進(jìn)行權(quán)限判斷的if( in_array( $url,$this->ignore_url ) ){return true;}return in_array( $url, $this->getRolePrivilege( ) ); }獲取某用戶的所有權(quán)限,取出指定用戶的所屬角色, 在通過(guò)角色取出所屬權(quán)限關(guān)系,在權(quán)限表中取出所有的權(quán)限鏈接
?
public function getRolePrivilege($uid = 0){if( !$uid && $this->current_user ){$uid = $this->current_user->id;}if( !$this->privilege_urls ){$role_ids = UserRole::find()->where([ 'uid' => $uid ])->select('role_id')->asArray()->column();if( $role_ids ){//在通過(guò)角色 取出 所屬 權(quán)限關(guān)系$access_ids = RoleAccess::find()->where([ 'role_id' => $role_ids ])->select('access_id')->asArray()->column();//在權(quán)限表中取出所有的權(quán)限鏈接$list = Access::find()->where([ 'id' => $access_ids ])->all();if( $list ){foreach( $list as $_item ){$tmp_urls = @json_decode( $_item['urls'],true );$this->privilege_urls = array_merge( $this->privilege_urls,$tmp_urls );}}}}return $this->privilege_urls ; }# 結(jié)語(yǔ)
權(quán)限系統(tǒng)可以說(shuō)是整個(gè)系統(tǒng)中最基礎(chǔ),同時(shí)也可以很復(fù)雜的,在實(shí)際項(xiàng)目中,會(huì)遇到多個(gè)系統(tǒng),多個(gè)用戶類型,多個(gè)使用場(chǎng)景,這就需要具體問(wèn)題具體分析,但最核心的RBAC模型是不變的,我們可以在其基礎(chǔ)上進(jìn)行擴(kuò)展來(lái)滿足需求。
?
總結(jié)
以上是生活随笔為你收集整理的设计一个权限系统-RBAC的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: [ CTF ]【天格】战队WriteUp
- 下一篇: QQ浏览器赵俊:解读腾讯扶持HTM5游戏