您好,登錄后才能下訂單哦!
小編給大家分享一下ThinkPHP中RBAC是什么,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
RBAC是什么,能解決什么難題?
RBAC是Role-Based Access Control的首字母,譯成中文即基于角色的權(quán)限訪問(wèn)控制,說(shuō)白了也就是用戶(hù)通過(guò)角色與權(quán)限進(jìn)行關(guān)聯(lián)[其架構(gòu)靈感來(lái)源于操作系統(tǒng)的GBAC(GROUP-Based Access Control)的權(quán)限管理控制]。簡(jiǎn)單的來(lái)說(shuō),一個(gè)用戶(hù)可以擁有若干角色,每一個(gè)角色擁有若干權(quán)限。這樣,就構(gòu)造成“用戶(hù)-角色-權(quán)限”的授權(quán)模型。在這種模型中,用戶(hù)與角色之間,角色與權(quán)限之間,一般者是多對(duì)多的關(guān)系。其對(duì)應(yīng)關(guān)系如下:
在許多的實(shí)際應(yīng)用中,系統(tǒng)不只是需要用戶(hù)完成簡(jiǎn)單的注冊(cè),還需要對(duì)不同級(jí)別的用戶(hù)對(duì)不同資源的訪問(wèn)具有不同的操作權(quán)限。且在企業(yè)開(kāi)發(fā)中,權(quán)限管理系統(tǒng)也成了重復(fù)開(kāi)發(fā)效率最高的一個(gè)模塊之一。而在多套系統(tǒng)中,對(duì)應(yīng)的權(quán)限管理只能滿足自身系統(tǒng)的管理需要,無(wú)論是在數(shù)據(jù)庫(kù)設(shè)計(jì)、權(quán)限訪問(wèn)和權(quán)限管理機(jī)制方式上都可能不同,這種不致性也就存在如下的憋端:
? 維護(hù)多套系統(tǒng),重復(fù)造輪子,時(shí)間沒(méi)用在刀刃上
? 用戶(hù)管理、組織機(jī)制等數(shù)據(jù)重復(fù)維護(hù),數(shù)據(jù)的完整性、一致性很難得到保障
? 權(quán)限系統(tǒng)設(shè)計(jì)不同,概念理解不同,及相應(yīng)技術(shù)差異,系統(tǒng)之間集成存在問(wèn)題,單點(diǎn)登錄難度大,也復(fù)雜的企業(yè)系統(tǒng)帶來(lái)困難
RBAC是基于不斷實(shí)踐之后,提出的一個(gè)比較成熟的訪問(wèn)控制方案。實(shí)踐表明,采用基于RBAC模型的權(quán)限管理系統(tǒng)具有以下優(yōu)勢(shì):由于角色、權(quán)限之間的變化比角色、用戶(hù)關(guān)系之間的變化相對(duì)要慢得多,減小了授權(quán)管理的復(fù)雜性,降低管理開(kāi)銷(xiāo);而且能夠靈活地支持應(yīng)用系統(tǒng)的安全策略,并對(duì)應(yīng)用系統(tǒng)的變化有很大的伸縮性;在操作上,權(quán)限分配直觀、容易理解,便于使用;分級(jí)權(quán)限適合分層的用戶(hù)級(jí)形式;重用性強(qiáng)。
ThinkPHP中RBAC實(shí)現(xiàn)體系
ThinkPHP中RBAC基于Java的Spring的Acegi安全系統(tǒng)作為參考原型,并做了相應(yīng)的簡(jiǎn)化處理,以適應(yīng)當(dāng)前的ThinkPHP結(jié)構(gòu),提供一個(gè)多層、可定制的安全體系來(lái)為應(yīng)用開(kāi)發(fā)提供安全控制。安全體系中主要有以下幾部分:
? 安全攔截器
? 認(rèn)證管理器
? 決策訪問(wèn)管理器
? 運(yùn)行身份管理器
安全攔截器
安全攔截器就好比一道道門(mén),在系統(tǒng)的安全防護(hù)系統(tǒng)中可能存在很多不同的安全控制環(huán)節(jié),一旦某個(gè)環(huán)節(jié)你未通過(guò)安全體系認(rèn)證,那么安全攔截器就會(huì)實(shí)施攔截。
認(rèn)證管理器
防護(hù)體系的第一道門(mén)就是認(rèn)證管理器,認(rèn)證管理器負(fù)責(zé)決定你是誰(shuí),一般它通過(guò)驗(yàn)證你的主體(通常是一個(gè)用戶(hù)名)和你的憑證(通常是一個(gè)密碼),或者更多的資料來(lái)做到。更簡(jiǎn)單的說(shuō),認(rèn)證管理器驗(yàn)證你的身份是否在安全防護(hù)體系授權(quán)范圍之內(nèi)。
訪問(wèn)決策管理
雖然通過(guò)了認(rèn)證管理器的身份驗(yàn)證,但是并不代表你可以在系統(tǒng)里面肆意妄為,因?yàn)槟氵€需要通過(guò)訪問(wèn)決策管理這道門(mén)。訪問(wèn)決策管理器對(duì)用戶(hù)進(jìn)行授權(quán),通過(guò)考慮你的身份認(rèn)證信息和與受保護(hù)資源關(guān)聯(lián)的安全屬性決定是是否可以進(jìn)入系統(tǒng)的某個(gè)模塊,和進(jìn)行某項(xiàng)操作。例如,安全規(guī)則規(guī)定只有主管才允許訪問(wèn)某個(gè)模塊,而你并沒(méi)有被授予主管權(quán)限,那么安全攔截器會(huì)攔截你的訪問(wèn)操作。
決策訪問(wèn)管理器不能單獨(dú)運(yùn)行,必須首先依賴(lài)認(rèn)證管理器進(jìn)行身份確認(rèn),因此,在加載訪問(wèn)決策過(guò)濾器的時(shí)候已經(jīng)包含了認(rèn)證管理器和決策訪問(wèn)管理器。
為了滿足應(yīng)用的不同需要,ThinkPHP 在進(jìn)行訪問(wèn)決策管理的時(shí)候采用兩種模式:登錄模式和即時(shí)模式。登錄模式,系統(tǒng)在用戶(hù)登錄的時(shí)候讀取改用戶(hù)所具備的授權(quán)信息到 Session,下次不再重新獲取授權(quán)信息。也就是說(shuō)即使管理員對(duì)該用戶(hù)進(jìn)行了權(quán)限修改,用戶(hù)也必須在下次登錄后才能生效。即時(shí)模式就是為了解決上面的問(wèn)題,在每次訪問(wèn)系統(tǒng)的模塊或者操作時(shí)候,進(jìn)行即使驗(yàn)證該用戶(hù)是否具有該模塊和操作的授權(quán),從更高程度上保障了系統(tǒng)的安全。
運(yùn)行身份管理器
運(yùn)行身份管理器的用處在大多數(shù)應(yīng)用系統(tǒng)中是有限的,例如某個(gè)操作和模塊需要多個(gè)身份的安全需求,運(yùn)行身份管理器可以用另一個(gè)身份替換你目前的身份,從而允許你訪問(wèn)應(yīng)用系統(tǒng)內(nèi)部更深處的受保護(hù)對(duì)象。這一層安全體系目前的 RBAC 中尚未實(shí)現(xiàn)。
ThinkPHP中RBAC認(rèn)證流程
對(duì)應(yīng)上面的安全體系,ThinkPHP 的 RBAC 認(rèn)證的過(guò)程大致如下:
1、判斷當(dāng)前模塊的當(dāng)前操作是否需要認(rèn)證
2、如果需要認(rèn)證并且尚未登錄,跳到認(rèn)證網(wǎng)關(guān),如果已經(jīng)登錄 執(zhí)行5
3、通過(guò)委托認(rèn)證進(jìn)行用戶(hù)身份認(rèn)證
4、獲取用戶(hù)的決策訪問(wèn)列表
5、判斷當(dāng)前用戶(hù)是否具有訪問(wèn)權(quán)限
權(quán)限管理的具體實(shí)現(xiàn)過(guò)程
RBAC相關(guān)的數(shù)據(jù)庫(kù)介紹
在ThinkPHP完整包,包含了RBAC處理類(lèi)RBAC.class.php文件,
位于Extend/Library/ORG/Util
。打開(kāi)該文件,其中就包含了使用RBAC必備的4張表,SQL語(yǔ)句如下(復(fù)制后請(qǐng)?zhí)鎿Q表前綴):
CREATE TABLE IF NOT EXISTS `think_access` ( `role_id` smallint(6) unsigned NOT NULL, `node_id` smallint(6) unsigned NOT NULL, `level` tinyint(1) NOT NULL, `module` varchar(50) DEFAULT NULL, KEY `groupId` (`role_id`), KEY `nodeId` (`node_id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8;CREATE TABLE IF NOT EXISTS `think_node` ( `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL, `title` varchar(50) DEFAULT NULL, `status` tinyint(1) DEFAULT '0', `remark` varchar(255) DEFAULT NULL, `sort` smallint(6) unsigned DEFAULT NULL, `pid` smallint(6) unsigned NOT NULL, `level` tinyint(1) unsigned NOT NULL, PRIMARY KEY (`id`), KEY `level` (`level`), KEY `pid` (`pid`), KEY `status` (`status`), KEY `name` (`name`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8;CREATE TABLE IF NOT EXISTS `think_role` ( `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL, `pid` smallint(6) DEFAULT NULL, `status` tinyint(1) unsigned DEFAULT NULL, `remark` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), KEY `pid` (`pid`), KEY `status` (`status`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ;CREATE TABLE IF NOT EXISTS `think_role_user` ( `role_id` mediumint(9) unsigned DEFAULT NULL, `user_id` char(32) DEFAULT NULL, KEY `group_id` (`role_id`), KEY `user_id` (`user_id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
下面對(duì)RBAC相關(guān)的數(shù)據(jù)庫(kù)表及字段作一下介紹:
表名 | 字段名 | 字段類(lèi)型 | 作用 |
---|---|---|---|
ly_user | id | INT | 用戶(hù)ID(唯一識(shí)別號(hào)) |
username | VARCHAR(16) | 用戶(hù)名 | |
password | VARCHAR(32) | 密碼 | |
VARCHAR(100) | 用戶(hù)郵箱 | ||
create_time | TIMESTAMP | 創(chuàng)建時(shí)間(時(shí)間戳) | |
logintime | TIMESTAMP | 最近一次登錄時(shí)間(時(shí)間戳) | |
loginip | VARCHAR(15) | 最近登錄的IP地址 | |
status | TINYINT(1) | 啟用狀態(tài):0:表示禁用;1:表示啟用 | |
remark | VARCHAR(255) | 備注信息 | |
ly_role | id | INT | 角色I(xiàn)D |
name | VARCHAR(20) | 角色名稱(chēng) | |
pid | SMALLINT(6) | 父角色對(duì)應(yīng)ID | |
status | TINYINT(1) | 啟用狀態(tài)(同上) | |
remark | VARCHAR(255) | 備注信息 | |
ly_node | id | SMALLINT(6) | 節(jié)點(diǎn)ID |
name | VARCHAR(20) | 節(jié)點(diǎn)名稱(chēng)(英文名,對(duì)應(yīng)應(yīng)用控制器、應(yīng)用、方法名) | |
title | VARCHAR(50) | 節(jié)點(diǎn)中文名(方便看懂) | |
status | TINYINT(1) | 啟用狀態(tài)(同上) | |
remark | VARCHAR(255) | 備注信息 | |
sort | SMALLINT(6) | 排序值(默認(rèn)值為50) | |
pid | SMALLINT(6) | 父節(jié)點(diǎn)ID(如:方法pid對(duì)應(yīng)相應(yīng)的控制器) | |
level | TINYINT(1) | 節(jié)點(diǎn)類(lèi)型:1:表示應(yīng)用(模塊);2:表示控制器;3:表示方法 | |
ly_role_user | user_id | INT | 用戶(hù)ID |
role_id | SMALLINT(6) | 角色I(xiàn)D | |
ly_access | role_id | SMALLINT(6) | 角色I(xiàn)D |
node_id | SMALLINT(6) | 節(jié)點(diǎn)ID | |
level | |||
module |
以下是數(shù)據(jù)庫(kù)表各字段的關(guān)聯(lián)關(guān)系:
實(shí)現(xiàn)RBAC管理的前導(dǎo)性工作
基于ThinkPHP實(shí)現(xiàn)RBAC的權(quán)限管理系統(tǒng)中,首先要做一些前導(dǎo)性的工作(系統(tǒng)數(shù)據(jù)庫(kù)設(shè)計(jì)TP已經(jīng)為我們完成了),主要分以下幾個(gè)方面:
? 用戶(hù)(增、刪、改、查)
? 角色(增、刪、改、查)
? 節(jié)點(diǎn)(增、刪、改、查)
? 配置權(quán)限(更新權(quán)限)
具體實(shí)現(xiàn)的代碼如下(相關(guān)解釋均在注釋之中):
<?php /** * */ namespace Home\Controller; use Home\Controller\BaseController; use Home\Model\AdminUserModel; use Org\Util\Tree; use Think\Page; class RbacController extends BaseController { //初始化操作 public function _initialize() { if (!IS_AJAX) $this->error('你訪問(wèn)的頁(yè)面不存在,請(qǐng)稍后再試'); } public function userIndex() { if (IS_POST) { $condition['username'] = array('like', "%" . trim(I('keybord')) . "%"); $model = D('AdminUser'); $count = $model->where($condition)->count(); $Page = new Page($count, 3); $Page->setConfig('theme', '%FIRST% %UP_PAGE% %LINK_PAGE% %DOWN_PAGE% %END%'); $show = $Page->show(); //select search $list = $model->where($condition)->field('password', true)->relation(true)->limit($Page->firstRow . ',' . $Page->listRows)->select(); $this->show = $show; $this->list = $list; $this->display('AdminUser/index'); } else { $model = D('AdminUser'); $count = $model->count(); $Page = new Page($count, 6); $Page->setConfig('header', '共%TOTAL_ROW%條'); $Page->setConfig('first', '首頁(yè)'); $Page->setConfig('last', '共%TOTAL_PAGE%頁(yè)'); $Page->setConfig('prev', '上一頁(yè)'); $Page->setConfig('next', '下一頁(yè)'); $Page->setConfig('link', 'indexpagenumb'); $Page->setConfig('theme', '%FIRST% %UP_PAGE% %LINK_PAGE% %DOWN_PAGE% %END%'); $show = $Page->show(); //select search $list = $model->field('password', true)->relation(true)->limit($Page->firstRow . ',' . $Page->listRows)->select(); $this->show = $show; $this->list = $list; $this->display('Rbac/userIndex'); } } /* * 平臺(tái)用戶(hù)異步驗(yàn)證 */ public function checkUser() { $username = trim(I('username')); $conditions = array('username' => ':username'); $result = M('AdminUser')->where($conditions)->bind(':username', $username)->find(); //如果不存在,則可以創(chuàng)建,也就是返回的是true if (!$result) { echo 'true'; } else { echo 'false'; } exit(); } /* * 創(chuàng)建平臺(tái)用戶(hù),這里開(kāi)啟了服務(wù)器驗(yàn)證 */ public function createAdminUser() { if (IS_POST) { /** * [實(shí)例化User對(duì)象] * D方法實(shí)例化模型類(lèi)的時(shí)候通常是實(shí)例化某個(gè)具體的模型類(lèi),如果你僅僅是對(duì)數(shù)據(jù)表進(jìn)行基本的CURD操作的話, * 使用M方法實(shí)例化的話,由于不需要加載具體的模型類(lèi),所以性能會(huì)更高。 */ $model = D('AdminUser'); /** * 如果創(chuàng)建失敗 表示驗(yàn)證沒(méi)有通過(guò) 輸出錯(cuò)誤提示信息 * $model->create() 會(huì)自動(dòng)調(diào)用驗(yàn)證規(guī)則 */ if (!$model->create()) return $this->error($model->getError()); //$username = $model->username; //$model->add() 插入數(shù)據(jù)后會(huì)自動(dòng)的摧毀數(shù)據(jù) if (!$uid = $model->add()) return $this->success('注冊(cè)失敗', U('Rbac/userIndex'));//獲取用戶(hù)id // 如果是注冊(cè)用戶(hù)的話 // session('uid', $uid); // session('username', $username); //用戶(hù)添加成功后,給用戶(hù)角色表添加數(shù)據(jù) $role['role_id'] = I('role_id'); $role['user_id'] = $uid; //添加該管理員操作到操作日志中 $desc = '給ID為:[' . $_POST['role_id'] . ']的角色,新增用戶(hù):[' . $_POST['username'] . '],密碼為:[' . $_POST['password'] . ']' . '其他參數(shù)' . $_POST; addOperationLog($desc); if (D('AdminRoleUser')->add($role)) { return $this->success('添加平臺(tái)用戶(hù)成功', U('Rbac/userIndex')); } else { return $this->error('添加平臺(tái)用戶(hù)失敗', U('Rbac/userIndex')); } return $this->success('添加平臺(tái)用戶(hù)成功', U('Rbac/userIndex')); } $this->role_list = M('AdminRole')->select(); $this->display('Rbac/createAdminUser'); } /** * 改變用戶(hù)角色 * 可以支持不執(zhí)行SQL而只是返回SQL語(yǔ)句:$User->fetchSql(true)->add($data); */ public function updateUser() { $userId = I('get.id'); if (IS_POST) { $data['user_id'] = I('post.user_id'); $data['role_id'] = I('post.role_id'); $model = M('AdminRoleUser'); if ($model->where(array('user_id' => $data['user_id']))->delete() == false) { return $this->error('用戶(hù)角色修改失敗', U('Rbac/updateUser', array('id' => $userId))); } if ($model->add($data) == false) { return $this->error('用戶(hù)角色修改失敗', U('Rbac/updateUser', array('id' => $userId))); } return $this->success('用戶(hù)角色修改成功', U('Rbac/userIndex')); } $this->role_list = M('AdminRole')->select(); $this->user = M('AdminUser')->join('tour_admin_role_user ON tour_admin_role_user.user_id = tour_admin_user.id')->where(array('id' => $userId))->field('user_id,username,role_id')->find(); $this->display(); } //刪除用戶(hù) public function delUser() { $user_id = I('post.id', '', 'int'); $user = D('AdminUser'); $result = $user->relation(true)->where(array('id' => $user_id))->delete(); if ($result) { //添加該管理員操作到操作日志中 $desc = '刪除用戶(hù)ID:' . $user_id . '成功'; addOperationLog($desc); $response = ['status' => 200, 'errmsg' => '刪除成功', 'dataList' => $result]; return $this->ajaxReturn($response, 'JSON'); } //添加該管理員操作到操作日志中 $desc = '刪除用戶(hù)ID:' . $user_id . '失敗'; addOperationLog($desc); $response = ['status' => 500, 'errmsg' => '刪除失敗', 'dataList' => $result]; return $this->ajaxReturn($response, 'JSON'); } //設(shè)置用戶(hù)狀態(tài) public function userStatus() { $uid = I('post.id'); $db = M('AdminUser'); $status = $db->where(array('id' => $uid))->getField('status'); $status = ($status == 1) ? 0 : 1; if ($db->where(array('id' => $uid))->setField('status', $status)) { $response = ['status' => 200, 'errmsg' => '改變成功', 'dataList' => $status]; return $this->ajaxReturn($response, 'JSON'); } //添加該管理員操作到操作日志中 $desc = '設(shè)置用戶(hù)狀態(tài):' . $uid . '失敗'; addOperationLog($desc); $response = ['status' => 500, 'errmsg' => '改變失敗', 'dataList' => $status]; return $this->ajaxReturn($response, 'JSON'); } /***********************************節(jié)點(diǎn)開(kāi)始****************************************************/ public function nodeIndex() { $db = M('AdminNode'); $node = $db->order('id')->select(); $this->nodelist = Tree::create($node); $this->display('Rbac/nodeIndex'); } //創(chuàng)建權(quán)限表單處理 public function createNode() { $db = M('AdminNode'); //創(chuàng)建權(quán)限表單處理 if (IS_POST) { $db->create(); if (!$db->add()) { return $this->error("權(quán)限添加失敗", U('Rbac/nodeIndex')); } return $this->success('權(quán)限添加成功', U('Rbac/nodeIndex')); } $node = $db->where('level !=3')->order('sort')->select(); $this->nodelist = Tree::create($node); $this->display(); } /* * 刪除權(quán)限 */ public function delNode() { $result = M('AdminNode')->where(array('id' => I('post.id', '', 'int')))->delete(); if ($result) { $response = ['status' => 200, 'errmsg' => '刪除成功', 'dataList' => $result]; return $this->ajaxReturn($response, 'JSON'); } $response = ['status' => 500, 'errmsg' => '刪除失敗', 'dataList' => $result]; return $this->ajaxReturn($response, 'JSON'); } /* * 設(shè)置權(quán)限狀態(tài) */ public function NodeStatus() { $id = I('post.id'); $db = M('AdminNode'); $status = $db->where(array('id' => $id))->getField('status'); $status = ($status == 1) ? 0 : 1; if ($db->where(array('id' => $id))->setField('status', $status)) { $response = ['status' => 200, 'errmsg' => '修改成功', 'dataList' => $status]; return $this->ajaxReturn($response, 'JSON'); } $response = ['status' => 500, 'errmsg' => '修改失敗', 'dataList' => $status]; return $this->ajaxReturn($response, 'JSON'); } /* * 該節(jié)點(diǎn)是否在菜單欄顯示 */ public function showMenus() { $id = I('post.id'); $db = M('AdminNode'); $show = $db->where(array('id' => $id))->getField('menus'); $menus = ($show == 1) ? 0 : 1; $result = $db->where(array('id' => $id))->setField('menus', $menus); if ($result) { $response = ['status' => 200, 'errmsg' => '修改成功', 'dataList' => $result]; return $this->ajaxReturn($response, 'JSON'); } $response = ['status' => 500, 'errmsg' => '修改失敗', 'dataList' => $result]; return $this->ajaxReturn($response, 'JSON'); } /***********************************角色開(kāi)始****************************************************/ public function roleIndex() { $db = M('AdminRole'); $this->rolelist = $db->select(); $this->display(); } /* *創(chuàng)建角色 */ public function createAdminRole() { if (IS_POST) { $name = I('post.name', '', 'strip_tags'); $remark = I('post.remark', '', 'strip_tags'); $pid = I('post.pid', '', 'strip_tags'); // 用strip_tags過(guò)濾$_GET['title'] if (empty($name)) return $this->error('角色名稱(chēng)不能為空'); $role = M('AdminRole'); $where['name'] = ':name'; $roleName = $role->where($where)->bind(':name', $name, \PDO::PARAM_STR)->getField('name'); if ($roleName) return $this->error("角色名稱(chēng):'" . $name . "'已經(jīng)存在", U('Rbac/roleIndex')); $role->name = $name; $role->remark = $remark; $role->pid = $pid; //create方法并不算是連貫操作,因?yàn)槠浞祷刂悼赡苁遣紶栔?,所以必須要進(jìn)行嚴(yán)格判斷。 if ($role->create()) { // 如果主鍵是自動(dòng)增長(zhǎng)型 成功后返回值就是最新插入的值 $result = $role->field('name,remark,pid')->add(); //如果在add方法之前調(diào)用field方法,則表示只允許寫(xiě)入指定的字段數(shù)據(jù),其他非法字段將會(huì)被過(guò)濾 if (!$result) return $this->error("角色添加失敗", U('Rbac/createpartent')); return $this->success('角色添加成功', U('Rbac/roleIndex')); } return $this->success('角色添加成功', U('Rbac/roleIndex')); } $this->display(); } /* *添加權(quán)限Node位權(quán)限表,Access為權(quán)限-角色關(guān)聯(lián)表 */ public function addNode() { $rid = I('rid', '', 'int'); if (!is_numeric($rid)) return $this->success('參數(shù)類(lèi)型錯(cuò)誤,必須是數(shù)字', U('Rbac/roleIndex')); //getFieldById針對(duì)某個(gè)字段(ID)查詢(xún)并返回某個(gè)字段(name)的值 $roleModel = M('AdminRole'); $roleWhere['id'] = ':id'; $role_name = $roleModel->where($roleWhere)->bind(':id', $rid, \PDO::PARAM_INT)->getField('name'); if ($role_name == false) return $this->success('沒(méi)有找到該角色', U('Rbac/roleIndex')); //根據(jù)角色遍歷所有權(quán)限 $access = M('AdminAccess'); if (IS_POST) { $actions = I('post.actions'); try { $access->startTrans(); $where['role_id'] = ':role_id'; $mod1 = $access->where($where)->bind(':role_id', $rid)->delete(); if (!$mod1) $access->rollback(); $data = array(); foreach ($actions as $value) { $tmp = explode('_', $value); $data[] = array( 'role_id' => $rid, 'node_id' => $tmp[0], 'level' => $tmp[1] ); } if (!($access->addAll($data))) { $access->rollback(); } else { $access->commit(); } return $this->success('權(quán)限設(shè)置成功', U('Rbac/addNode', array('rid' => $rid))); } catch (\Exception $e) { $access->rollback(); return $this->success('權(quán)限設(shè)置異常', U('Rbac/addNode', array('rid' => $rid))); } } $node = M('AdminNode')->order('id')->select(); $node_list = Tree::create($node); $node_arr = array(); foreach ($node_list as $value) { $conditions['node_id'] = $value['id']; $conditions['role_id'] = $rid; $count = $access->where($conditions)->count(); if ($count) { $value['access'] = '1'; } else { $value['access'] = '0'; } $node_arr[] = $value; } $this->role_name = $role_name; $this->node_list = $node_arr; $this->rid = $rid; $this->display(); } /* *刪除角色以及角色所擁有的權(quán)限 */ public function delRole() { $role_id = I('post.role_id', '', 'int'); $user = D('AdminRole'); $result = $user->relation(true)->where(array('id' => $role_id))->delete(); if ($result) { $response = ['status' => 200, 'errmsg' => '修改成功', 'dataList' => $result]; return $this->ajaxReturn($response, 'JSON'); } $response = ['status' => 500, 'errmsg' => '修改失敗', 'dataList' => $result]; return $this->ajaxReturn($response, 'JSON'); } /* * 設(shè)置角色狀態(tài) */ public function roleStatus() { $rid = I('post.rid'); $db = M('AdminRole'); $status = $db->where(array('id' => $rid))->getField('status'); $status = ($status == 1) ? 0 : 1; if ($db->where(array('id' => $rid))->setField('status', $status)) { $response = ['status' => 200, 'errmsg' => '修改成功', 'dataList' => $status]; return $this->ajaxReturn($response, 'JSON'); } $response = ['status' => 500, 'errmsg' => '修改失敗', 'dataList' => $status]; return $this->ajaxReturn($response, 'JSON'); } }
ThinkPHP中RBAC類(lèi)的詳解
在ThinkPHP處理權(quán)限管理中,真正的難點(diǎn)并不是在RBAC類(lèi)的使用上,上面相關(guān)的處理(權(quán)限配置,節(jié)點(diǎn)管理等);所以當(dāng)完成以上工作,其實(shí)RBAC系統(tǒng)已經(jīng)完成了90%。下面先從ThinkPHP中RBAC的配置說(shuō)起(詳細(xì)請(qǐng)參看對(duì)應(yīng)的注釋內(nèi)容):
<?php return array( "USER_AUTH_ON" => true, //是否開(kāi)啟權(quán)限驗(yàn)證(必配) "USER_AUTH_TYPE" => 1, //驗(yàn)證方式(1、登錄驗(yàn)證;2、實(shí)時(shí)驗(yàn)證) "USER_AUTH_KEY" => 'uid', //用戶(hù)認(rèn)證識(shí)別號(hào)(必配) "ADMIN_AUTH_KEY" => 'superadmin', //超級(jí)管理員識(shí)別號(hào)(必配) "USER_AUTH_MODEL" => 'user', //驗(yàn)證用戶(hù)表模型 ly_user 'USER_AUTH_GATEWAY' => '/Public/login', //用戶(hù)認(rèn)證失敗,跳轉(zhuǎn)URL 'AUTH_PWD_ENCODER'=>'md5', //默認(rèn)密碼加密方式 "RBAC_SUPERADMIN" => 'admin', //超級(jí)管理員名稱(chēng) "NOT_AUTH_MODULE" => 'Index,Public', //無(wú)需認(rèn)證的控制器 "NOT_AUTH_ACTION" => 'index', //無(wú)需認(rèn)證的方法 'REQUIRE_AUTH_MODULE' => '', //默認(rèn)需要認(rèn)證的模塊 'REQUIRE_AUTH_ACTION' => '', //默認(rèn)需要認(rèn)證的動(dòng)作 'GUEST_AUTH_ON' => false, //是否開(kāi)啟游客授權(quán)訪問(wèn) 'GUEST_AUTH_ID' => 0, //游客標(biāo)記 "RBAC_ROLE_TABLE" => 'ly_role', //角色表名稱(chēng)(必配) "RBAC_USER_TABLE" => 'ly_role_user', //用戶(hù)角色中間表名稱(chēng)(必配) "RBAC_ACCESS_TABLE" => 'ly_access', //權(quán)限表名稱(chēng)(必配) "RBAC_NODE_TABLE" => 'ly_node', //節(jié)點(diǎn)表名稱(chēng)(必配) );
注意:
? 以上有的配置項(xiàng)并非必須的,但標(biāo)有“必配”,則必須配置
? 無(wú)需認(rèn)證的權(quán)限和方法與需要認(rèn)證的模塊和動(dòng)作可以根據(jù)需要選擇性配置,還有部分權(quán)限相關(guān)配置并未列表出
RBAC處理類(lèi)提供靜態(tài)的方法
ThinkPHP的RBAC處理類(lèi)提供給我們很多相關(guān)的靜態(tài)方法如下:
方法名 | 接收參數(shù)說(shuō)明 | 返回值 | 說(shuō)明 |
RBAC::authenticate(map,map,model=''); | $map:ThinkPHP數(shù)據(jù)庫(kù)處理類(lèi)的where條件參 數(shù)$model:用戶(hù)表名,默認(rèn)取配置文件C('USER_AUTH_MODEL') | array | RBAC::authenticate(array("username"=>"admin","userpwd" => I('POST.pwd','', 'md5'))); 返回值是在用戶(hù)表中,以$map為條件where的查閱結(jié)果集 |
0RBAC::saveAccessList($authId=null); | $authId:用戶(hù)識(shí)別號(hào),默認(rèn)取C('USER_AUTH_KEY'); | 返回一個(gè)空值 | 如果驗(yàn)證方式為登錄驗(yàn)證,則將權(quán)限寫(xiě)入session中,否則不作任何處理 |
RBAC::getRecordAccessList(authId=null,authId=null,module=''); | $authId:用戶(hù)識(shí)別號(hào)(可不傳) $module:當(dāng)前操作的模塊名稱(chēng) | Array | 返回一個(gè)包含權(quán)限的ID的數(shù)組 |
RBAC::checkAccess() | 無(wú) | 返回true或false | 檢查當(dāng)前操作是否需要認(rèn)證(根據(jù)配置中需要認(rèn)證和不需要評(píng)論的模塊或方法得出) |
RBAC::checkLogin() | 無(wú) | true | 如果當(dāng)前操作需要認(rèn)證且用戶(hù)沒(méi)有登錄,繼續(xù)檢測(cè)是否開(kāi)啟游客授權(quán)。如果開(kāi)啟游客授權(quán),則寫(xiě)入游客權(quán)限;否則跳到登錄頁(yè) |
RBAC::AccessDecision($appName=APP_NAME) | $appName:選傳,有默認(rèn)值 | true:表示有操作權(quán)限 false:無(wú)操作權(quán)限 | AccessDecision(appName=APPNAME)方法,檢測(cè)當(dāng)前項(xiàng)目模塊操作,是否存在于appName=APPNAME)方法,檢測(cè)當(dāng)前項(xiàng)目模塊操作,是否存在于_SESSION['_ACCESS_LIST']數(shù)組中$_SESSION['_ACCESS_LIST']['當(dāng)前操作']['當(dāng)前模塊']['當(dāng)前操作']是否存在。如果存在表示有權(quán)限,返回true;否則返回flase。 |
RBAC::getAccessList($authId) | $authId:用戶(hù)識(shí)別號(hào)(選傳,程序自動(dòng)獲取) | Array | 通過(guò)數(shù)據(jù)庫(kù)查詢(xún)?nèi)〉卯?dāng)前認(rèn)證號(hào)的所有權(quán)限列表 |
RBAC::getModuleAccessList(authId,authId,module) | $authId:用戶(hù)識(shí)別號(hào)(必)$module:對(duì)應(yīng)的模塊(必) | Array | 返回指定用戶(hù)可訪問(wèn)的節(jié)點(diǎn)權(quán)限數(shù)組 |
注意:在使用RBAC::AccessDecision()
方法時(shí),如果你開(kāi)啟了項(xiàng)目分組,則必須傳入當(dāng)前分組,代碼如下:
//權(quán)限驗(yàn)證 if(C('USER_AUTH_ON') && !$notAuth) { import('ORG.Util.RBAC'); //使用了項(xiàng)目分組,則必須引入GROUP_NAME RBAC::AccessDecision(GROUP_NAME) || $this->error("你沒(méi)有對(duì)應(yīng)的權(quán)限"); }
在完成用戶(hù)登錄,角色創(chuàng)建,節(jié)點(diǎn)增刪改查的工作后,就只剩下了RBAC如何在對(duì)應(yīng)程序代碼中應(yīng)用了。挻簡(jiǎn)單的,只用在原來(lái)的代碼其他上改動(dòng)幾個(gè)地方即可。
? 用戶(hù)登錄時(shí),寫(xiě)入用戶(hù)權(quán)限
? 用戶(hù)操作時(shí),進(jìn)行權(quán)限驗(yàn)證
下面是用戶(hù)登錄時(shí)的實(shí)現(xiàn)代碼:
<?php namespace Home\Controller; use Think\Controller; use Org\Util\Rbac; class LoginController extends Controller { public function index() { $this->display(); } /* * 異步驗(yàn)證賬號(hào) */ public function checkUser() { $username = I('username'); $conditions = array('username' => ':username'); $result = M('User')->where($conditions)->bind(':username', $username)->find(); //如果不存在,則可以創(chuàng)建,也就是返回的是true if (!$result) { echo 'false'; } else { echo 'true'; } exit(); } /* * 異步驗(yàn)證密碼 */ public function checkPwd() { $username = I('post.username'); $password = I('post.password'); $conditions = array('username' => ':username'); $result = M('User')->where($conditions)->bind(':username', $username)->find(); if (md5($password) != $result['password']) { echo 'false'; } else { echo 'true'; } exit(); } /* * 檢查登錄 */ public function checkLogin() { if (!IS_POST) $this->error('非法訪問(wèn)'); // 采用htmlspecialchars方法對(duì)$_GET['name'] 進(jìn)行過(guò)濾,如果不存在則返回空字符串 $username = I('post.username', '', 'htmlspecialchars'); // 采用正則表達(dá)式進(jìn)行變量過(guò)濾,如果正則匹配不通過(guò)的話,則返回默認(rèn)值。 //I('get.name','','/^[A-Za-z]+$/'); $password = md5(I('post.password')); $user = D('AdminUser'); $where = array('username' => $username); $fields = array('id', 'password', 'username', 'status', 'expire', 'logintime'); // 之查找需要的字段 $result = $user->where($where)->field($fields)->find(); if (!$result || $password != $result['password']) return $this->error('賬號(hào)或密碼錯(cuò)誤',U('Home/Login/index')); if ($result['status'] == 0) return $this->error('該用戶(hù)被鎖定,暫時(shí)不可登錄',U('Home/Login/index')); // 是否記住我的登錄,設(shè)置一個(gè)Cookie,寫(xiě)在客戶(hù)端 if (isset($_POST['remember'])) { $value = $result['id'] . '|' . get_client_ip() . '|' . $result['username']; $value = encrytion($value, 1); @setcookie('remember', $value, C('AUTO_LOGIN_LIFETIME'), '/'); } // 每天登錄增加經(jīng)驗(yàn)值 $today = strtotime(date('Y-m-d')); // 獲取今天0時(shí)0分0秒的時(shí)間 // 如果上次的登錄時(shí)間小于今天的時(shí)間,則增加經(jīng)驗(yàn)值 $where2 = array('id' => $result['id']); if ($result['logintime'] < $today) { $user->where($where2)->setInc('expire', 10); } //更新登錄戶(hù)登錄信息 $data_arr = array( 'id' => $result['id'], 'logintime' => time(), 'loginip' => get_client_ip(), ); if ($user->save($data_arr)) { // 獲取$_SESSION['user_id'] 如果不存在則默認(rèn)為0 session('uid', 0); session('username', $result['username']); session('loginAccount', $result['username']); session('loginUserName', $result['username']); session('uid', $result['id']); //RBAC 開(kāi)始,用戶(hù)認(rèn)證SESSION標(biāo)記 ,默認(rèn)為"authId" session(C('USER_AUTH_KEY'), $result['id']); //如果為超級(jí)管理員,則無(wú)需驗(yàn)證 if ($_SESSION['username'] == C('RBAC_SUPERADMIN')) session(C('ADMIN_AUTH_KEY'), true); //用于檢測(cè)用戶(hù)權(quán)限的方法,并保存到Session中,讀取用戶(hù)權(quán)限 Rbac::saveAccessList($result['id']); //添加操作日志中 $desc = '登陸成功'; addOperationLog($desc); return $this->redirect('Index/index'); } else { return $this->error('2222222222222'); } } public function memberInfo() { $user_id = session('user_id'); $user = D('User'); $where = array('user_id' => $user_id); $result = $user->where($where)->select(); $this->result = $result; $this->display(); } /** * 獲取apikey_values */ public function apikey() { $secret = "6JNVkTk4jHsgF0e1oOVLwOZDeq83pDXa"; $user_id = I('user_id', '', int); // $where = array('user_id = %d ',array($user_id)); $user = M('User'); // $result = $user->where($where)->find(); $result = $user->where("user_id = %d", array($user_id))->find(); $hash = sha1($result['user_id'] . $result['password'] . $secret); $data = array( 'apikey_value' => $hash, 'apikey_time' => time() ); $where = array('user_id' => $user_id); $user->where($where)->save($data); $this->ajaxReturn($hash); } public function hash() { if (!IS_POST) { $out_data = array( 'err_msg' => 'request method is error.', 'is_success' => 'Fail' ); exit(json_encode($out_data)); }; $username = I('username'); $password = I('password'); $where = array('username' => $username); $user = M('User'); $result = $user->where($where)->find(); if (!$result || $password != $result['password']) { $out_data = array( 'err_msg' => 'Username or password is incorrect.', 'is_success' => 'Fail' ); exit(json_encode($out_data)); } /** * HASH生成規(guī)則 */ $secret = "6JNVkTk4jHsgF0e1oOVLwOZDeq83pDXa"; $hash = sha1($result['user_id'] . $result['password'] . $secret); $where = array('user_id' => $result['user_id']); $data["apikey_value"] = $hash; $data["apikey_time"] = time(); $user->where($where)->save($data); $out_data = array( 'is_success' => 'Success', 'hash' => $hash ); exit(json_encode($out_data)); } public function relationTest() { echo get_client_ip(); } public function error1(){ $this->display(); } public function logout() { session('username', NULL); session_unset(); session_destroy(); return $this->redirect('Login/index'); } }
接著在控制器目錄創(chuàng)建一個(gè)CommonAction.class.php
文件,然后改寫(xiě)所有要權(quán)限驗(yàn)證的類(lèi),讓其繼承自CommonAction
。代碼如下:
<?php namespace Home\Controller; use Think\Controller; use Org\Util\Rbac; class BaseController extends Controller { // /** // * ThikPHP自動(dòng)運(yùn)行方法,每一次,都會(huì)自動(dòng)運(yùn)行這個(gè)方法 // * 要判斷一個(gè)session值是否已經(jīng)設(shè)置,可以使用 // session('?name'); 相當(dāng)于:isset($_SESSION['name']); // */ public function _initialize(){ /***************************************網(wǎng)站開(kāi)關(guān)****************************************************/ if(!C('WEB_STATE')) exit('網(wǎng)站維護(hù)中'); /***********************************沒(méi)有登錄時(shí)候時(shí)需要執(zhí)行的代碼**************************************/ if(!isset($_SESSION[C('USER_AUTH_KEY')])) return $this->redirect('Login/index'); /***************************************自動(dòng)登錄配置************************************************/ if(isset($_COOKIE['remember']) && !isset($_SESSION['uid'])){ // Cookie 解密 $value = encrytion($_COOKIE['remember']); $result = explode('|',$value); // 判斷IP地址是否一樣 if($result[1] == get_client_ip()){ session('uid',$result[0]); session('uid',$result[2]); } } /***************************************權(quán)限認(rèn)證****************************************************/ $Public = in_array(MODULE_NAME,explode(',',C('NOT_AUTH_MODULE'))) || in_array(ACTION_NAME,explode(',',C('NOT_AUTH_ACTION'))); // 如果不在公共模塊之中,同時(shí)開(kāi)啟權(quán)限驗(yàn)證的話,則開(kāi)始認(rèn)證過(guò)程 if(C('USER_AUTH_ON') && !$Public) { if(!Rbac::AccessDecision()) //通過(guò)accessDecision獲取權(quán)限信息,true:表示有操作權(quán)限,false:無(wú)操作權(quán)限 { return $this->error("你沒(méi)有對(duì)應(yīng)的權(quán)限"); //沒(méi)有獲取到權(quán)限信息時(shí)需要執(zhí)行的代碼 } } /***************************************導(dǎo)航欄菜單顯示****************************************************/ /* * 思路: * 1.取出所有權(quán)限節(jié)點(diǎn)。 * 2.取出當(dāng)前登錄用戶(hù)擁有的模塊權(quán)限(取英文名稱(chēng))和操作權(quán)限(取ID) * 3.對(duì)所有權(quán)限進(jìn)行遍歷,先匹配模塊權(quán)限,不存在刪除,存在則匹配操作權(quán)限 */ // 超級(jí)管理員登錄 if(session(C('ADMIN_AUTH_KEY'))) { $menus = D('AdminNode')->where('level = 2')->relation(true)->order('sort desc')->select();//取出所有節(jié)點(diǎn) }else{ /** * [1]取出所有的權(quán)限,是通過(guò)關(guān)聯(lián)模型從數(shù)據(jù)庫(kù)中獲取的 */ $menus = D('AdminNode')->where('level = 2')->relation(true)->order('sort desc')->select(); $module = ''; //存放擁有的模塊 $node_id = ''; //存放擁有的模塊 /** * [2]獲取當(dāng)前用戶(hù)的所有權(quán)限,這個(gè)是從RBAC中獲取的 */ $access_list = $_SESSION['_ACCESS_LIST']; //當(dāng)前用戶(hù)所擁有的權(quán)限 foreach ($access_list as $key => $value) { foreach ($value as $key1 => $value1) { $module = $module.','.$key1; //字符串拼接模塊名稱(chēng) foreach ($value1 as $key2 => $value2) { $node_id = $node_id.','.$value2; //字符串拼操作id } } } /** * [3]去除沒(méi)有權(quán)限的節(jié)點(diǎn),通過(guò)所有權(quán)限和用戶(hù)已經(jīng)擁有的權(quán)限比較 */ foreach ($menus as $key => $value) { $all_node[] = $value['name']; if(!in_array(strtoupper($value['name']), explode(',', $module))){ unset($menus[$key]); //刪除模塊 }else{ //模塊存在,比較里面的操作 foreach ($value['node'] as $key1 => $value1) { if(!in_array($value1['id'], explode(',', $node_id))){ unset($menus[$key]['node'][$key1]); // 刪除操作 } } } } } $this->menus = $menus; } }
在ThinkPHP
中提供了一個(gè)_initialize()
方法,是在類(lèi)初始化就會(huì)執(zhí)行的,也就是只要后面控制器繼承自CommonAction
類(lèi),就會(huì)在作對(duì)應(yīng)操作時(shí),執(zhí)行_initialize()
方法。
以上是ThinkPHP中RBAC是什么的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。