RBAC是什麼,能解決什麼難題?
RBAC是Role-Based Access Control的首字母,譯成中文即基於角色的權限存取控制,說白了也就是用戶透過角色與權限進行關聯[其架構靈感來源於作業系統的GBAC(GROUP-Based Access Control)的權限管理控制]。簡單的來說,一個使用者可以擁有若干角色,每個角色擁有若干權限。這樣,就建構成「使用者-角色-權限」的授權模型。在這種模型中,使用者與角色之間,角色與權限之間,一般者是多對多的關係。其對應關係如下:
在許多的實際應用中,系統不只是需要使用者完成簡單的註冊,還需要對不同層級的使用者對不同資源的存取具有不同的操作權限。而在企業開發中,權限管理系統也成了重複開發效率最高的一個模組之一。而在多套系統中,對應的權限管理只能滿足自身系統的管理需要,無論是在資料庫設計、權限存取和權限管理機制方式上都可能不同,這種不致性也就存在如下的憋端:
• 維護多套系統,重複造輪子,時間沒用在刀刃上
• 用戶管理、組織機制等資料重複維護,資料的完整性、一致性很難得到保障
• 權限系統設計不同,概念理解不同,及相應技術差異,系統之間整合存在問題,單點登入難度大,也複雜的企業系統帶來困難
RBAC是基於不斷實踐之後,提出的一個比較成熟的存取控制方案。實踐表明,採用基於RBAC模型的權限管理系統具有以下優勢:由於角色、權限之間的變化比角色、用戶關係之間的變化相對要慢得多,減小了授權管理的複雜性,降低管理開銷;而且能夠靈活地支援應用系統的安全策略,並對應用系統的變化有很大的伸縮性;在操作上,權限分配直觀、容易理解,便於使用;分級權限適合分層的用戶級形式;重用性強。
ThinkPHP中RBAC實作系統
ThinkPHP中RBAC基於Java的Spring的Acegi安全系統作為參考原型,並做了相應的簡化處理,以適應當前的ThinkPHP結構,提供一個多層、可自訂的安全系統來為應用開發提供安全控制。安全系統中主要有以下幾個部分:
• 安全攔截器
• 認證管理器
• 決策存取管理器
##• 運行身分識別管理器安全攔截器
安全攔截器就好比一道道門,在系統的安全防護系統中可能存在許多不同的安全控制環節,一旦某個環節你未通過安全體系認證,那麼安全攔截器就會實施攔截。認證管理器
防護系統的第一道門就是認證管理器,認證管理器負責決定你是誰,一般它通過驗證你的主體(通常是一個使用者名稱)和你的憑證(通常是一個密碼),或更多的資料來做到。更簡單的說,認證管理器驗證你的身分是否在安全防護系統授權範圍內。存取決策管理
雖然通過了認證管理器的身份驗證,但並不代表你可以在系統裡面肆意妄為,因為你還需要透過存取決策管理這道門。存取決策管理器對使用者進行授權,透過考慮你的身分認證資訊和與受保護資源關聯的安全屬性決定是是否可以進入系統的某個模組,和進行某項操作。例如,安全規則規定只有主管才允許存取某個模組,而你並沒有被授予主管權限,那麼安全攔截器會攔截你的存取操作。決策存取管理器不能單獨執行,必須先依賴認證管理器進行身份確認,因此,在載入存取決策過濾器的時候已經包含了認證管理器和決策存取管理器。
執行身分識別管理器
運行身份管理器的用處在大多數應用系統中是有限的,例如某個操作和模組需要多個身份的安全需求,運行身份管理器可以用另一個身份替換你目前的身份,從而允許你訪問應用系統內部更深處的受保護物件。這一層安全體系目前的 RBAC 中尚未實現。
ThinkPHP中RBAC認證流程
對應上面的安全體系,ThinkPHP 的RBAC 認證的過程大致如下:
1、判斷目前模組的目前操作是否需要認證
2、如果需要認證且尚未登錄,跳到認證網關,如果已經登入執行5
3、透過委託認證進行使用者身分認證
4、取得使用者的決策存取清單
#5、判斷目前使用者是否具有存取權限
#權限管理的具體實作過程
RBAC相關的資料庫介紹
在ThinkPHP完整包,包含了RBAC處理類RBAC.class.php文件,
位於Extend/Library/ORG/Util
。開啟該文件,其中就包含了使用RBAC必備的4張表,SQL語句如下(複製後請替換表前綴):
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;
下面對RBAC相關的資料庫表及字段作一下介紹:
表名 | 欄位名稱 | 欄位類型 | 作用 |
---|---|---|---|
ly_user | id | INT | 使用者ID(唯一識別號碼) |
username | VARCHAR(16) | 使用者名稱 | |
password | VARCHAR(32) | 密碼 | |
VARCHAR(100) | 使用者信箱 | ||
create_time | TIMESTAMP | 建立時間(時間戳記) | |
logintime | TIMESTAMP | 最近一次登入時間(時間戳記) | |
loginip | VARCHAR(15) | 已登入的IP位址 | |
status | TINYINT(1) | 啟用狀態:0:表示停用;1:表示啟用 | |
remark | VARCHAR(255) | 備註資訊 | |
ly_role | id | INT | #角色ID |
name | VARCHAR(20) | 角色名稱 | |
pid | SMALLINT(6) | 父角色對應ID | |
#status | TINYINT(1) | 啟用狀態(同上) | |
remark | VARCHAR (255) | 備註資訊 | |
ly_node | id | SMALLINT(6) | 節點ID |
name | VARCHAR(20) | 節點名稱(英文名,對應應用程式控制器、應用程式、方法名稱) | |
title | VARCHAR(50) | 節點中文名稱(方便看懂) | |
status | TINYINT(1) | 啟用狀態(同上) | |
remark | VARCHAR(255) | 備註資訊 | |
sort | SMALLINT(6) | 排序值(預設值為50) | |
pid | SMALLINT(6) | 父節點ID(如:方法pid對應對應的控制器) | |
level | TINYINT(1) | 節點類型:1:表示應用程式(模組);2:表示控制器;3:表示方法 | |
ly_role_user | user_id | INT | 使用者ID |
role_id | #SMALLINT( 6) | 角色ID | |
ly_access | #role_id | SMALLINT(6) | 角色ID |
node_id | SMALLINT(6) | 「節點ID | |
level | |||
module | # |
以下是数据库表各字段的关联关系:
实现RBAC管理的前导性工作
基于ThinkPHP实现RBAC的权限管理系统中,首先要做一些前导性的工作(系统数据库设计TP已经为我们完成了),主要分以下几个方面:
• 用户(增、删、改、查)
• 角色(增、删、改、查)
• 节点(增、删、改、查)
• 配置权限(更新权限)
具体实现的代码如下(相关解释均在注释之中):
<?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('你访问的页面不存在,请稍后再试'); } 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', '首页'); $Page->setConfig('last', '共%TOTAL_PAGE%页'); $Page->setConfig('prev', '上一页'); $Page->setConfig('next', '下一页'); $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'); } } /* * 平台用户异步验证 */ public function checkUser() { $username = trim(I('username')); $conditions = array('username' => ':username'); $result = M('AdminUser')->where($conditions)->bind(':username', $username)->find(); //如果不存在,则可以创建,也就是返回的是true if (!$result) { echo 'true'; } else { echo 'false'; } exit(); } /* * 创建平台用户,这里开启了服务器验证 */ public function createAdminUser() { if (IS_POST) { /** * [实例化User对象] * D方法实例化模型类的时候通常是实例化某个具体的模型类,如果你仅仅是对数据表进行基本的CURD操作的话, * 使用M方法实例化的话,由于不需要加载具体的模型类,所以性能会更高。 */ $model = D('AdminUser'); /** * 如果创建失败 表示验证没有通过 输出错误提示信息 * $model->create() 会自动调用验证规则 */ if (!$model->create()) return $this->error($model->getError()); //$username = $model->username; //$model->add() 插入数据后会自动的摧毁数据 if (!$uid = $model->add()) return $this->success('注册失败', U('Rbac/userIndex'));//获取用户id // 如果是注册用户的话 // session('uid', $uid); // session('username', $username); //用户添加成功后,给用户角色表添加数据 $role['role_id'] = I('role_id'); $role['user_id'] = $uid; //添加该管理员操作到操作日志中 $desc = '给ID为:[' . $_POST['role_id'] . ']的角色,新增用户:[' . $_POST['username'] . '],密码为:[' . $_POST['password'] . ']' . '其他参数' . $_POST; addOperationLog($desc); if (D('AdminRoleUser')->add($role)) { return $this->success('添加平台用户成功', U('Rbac/userIndex')); } else { return $this->error('添加平台用户失败', U('Rbac/userIndex')); } return $this->success('添加平台用户成功', U('Rbac/userIndex')); } $this->role_list = M('AdminRole')->select(); $this->display('Rbac/createAdminUser'); } /** * 改变用户角色 * 可以支持不执行SQL而只是返回SQL语句:$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('用户角色修改失败', U('Rbac/updateUser', array('id' => $userId))); } if ($model->add($data) == false) { return $this->error('用户角色修改失败', U('Rbac/updateUser', array('id' => $userId))); } return $this->success('用户角色修改成功', 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(); } //删除用户 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 = '删除用户ID:' . $user_id . '成功'; addOperationLog($desc); $response = ['status' => 200, 'errmsg' => '删除成功', 'dataList' => $result]; return $this->ajaxReturn($response, 'JSON'); } //添加该管理员操作到操作日志中 $desc = '删除用户ID:' . $user_id . '失败'; addOperationLog($desc); $response = ['status' => 500, 'errmsg' => '删除失败', 'dataList' => $result]; return $this->ajaxReturn($response, 'JSON'); } //设置用户状态 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 = '设置用户状态:' . $uid . '失败'; addOperationLog($desc); $response = ['status' => 500, 'errmsg' => '改变失败', 'dataList' => $status]; return $this->ajaxReturn($response, 'JSON'); } /***********************************节点开始****************************************************/ public function nodeIndex() { $db = M('AdminNode'); $node = $db->order('id')->select(); $this->nodelist = Tree::create($node); $this->display('Rbac/nodeIndex'); } //创建权限表单处理 public function createNode() { $db = M('AdminNode'); //创建权限表单处理 if (IS_POST) { $db->create(); if (!$db->add()) { return $this->error("权限添加失败", U('Rbac/nodeIndex')); } return $this->success('权限添加成功', U('Rbac/nodeIndex')); } $node = $db->where('level !=3')->order('sort')->select(); $this->nodelist = Tree::create($node); $this->display(); } /* * 删除权限 */ 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'); } /* * 设置权限状态 */ 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'); } /* * 该节点是否在菜单栏显示 */ 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'); } /***********************************角色开始****************************************************/ public function roleIndex() { $db = M('AdminRole'); $this->rolelist = $db->select(); $this->display(); } /* *创建角色 */ 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过滤$_GET['title'] if (empty($name)) return $this->error('角色名称不能为空'); $role = M('AdminRole'); $where['name'] = ':name'; $roleName = $role->where($where)->bind(':name', $name, \PDO::PARAM_STR)->getField('name'); if ($roleName) return $this->error("角色名称:'" . $name . "'已经存在", U('Rbac/roleIndex')); $role->name = $name; $role->remark = $remark; $role->pid = $pid; //create方法并不算是连贯操作,因为其返回值可能是布尔值,所以必须要进行严格判断。 if ($role->create()) { // 如果主键是自动增长型 成功后返回值就是最新插入的值 $result = $role->field('name,remark,pid')->add(); //如果在add方法之前调用field方法,则表示只允许写入指定的字段数据,其他非法字段将会被过滤 if (!$result) return $this->error("角色添加失败", U('Rbac/createpartent')); return $this->success('角色添加成功', U('Rbac/roleIndex')); } return $this->success('角色添加成功', U('Rbac/roleIndex')); } $this->display(); } /* *添加权限Node位权限表,Access为权限-角色关联表 */ public function addNode() { $rid = I('rid', '', 'int'); if (!is_numeric($rid)) return $this->success('参数类型错误,必须是数字', U('Rbac/roleIndex')); //getFieldById针对某个字段(ID)查询并返回某个字段(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('没有找到该角色', U('Rbac/roleIndex')); //根据角色遍历所有权限 $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('权限设置成功', U('Rbac/addNode', array('rid' => $rid))); } catch (\Exception $e) { $access->rollback(); return $this->success('权限设置异常', 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(); } /* *删除角色以及角色所拥有的权限 */ 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'); } /* * 设置角色状态 */ 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类的详解
在ThinkPHP处理权限管理中,真正的难点并不是在RBAC类的使用上,上面相关的处理(权限配置,节点管理等);所以当完成以上工作,其实RBAC系统已经完成了90%。下面先从ThinkPHP中RBAC的配置说起(详细请参看对应的注释内容):
<?php return array( "USER_AUTH_ON" => true, //是否开启权限验证(必配) "USER_AUTH_TYPE" => 1, //验证方式(1、登录验证;2、实时验证) "USER_AUTH_KEY" => 'uid', //用户认证识别号(必配) "ADMIN_AUTH_KEY" => 'superadmin', //超级管理员识别号(必配) "USER_AUTH_MODEL" => 'user', //验证用户表模型 ly_user 'USER_AUTH_GATEWAY' => '/Public/login', //用户认证失败,跳转URL 'AUTH_PWD_ENCODER'=>'md5', //默认密码加密方式 "RBAC_SUPERADMIN" => 'admin', //超级管理员名称 "NOT_AUTH_MODULE" => 'Index,Public', //无需认证的控制器 "NOT_AUTH_ACTION" => 'index', //无需认证的方法 'REQUIRE_AUTH_MODULE' => '', //默认需要认证的模块 'REQUIRE_AUTH_ACTION' => '', //默认需要认证的动作 'GUEST_AUTH_ON' => false, //是否开启游客授权访问 'GUEST_AUTH_ID' => 0, //游客标记 "RBAC_ROLE_TABLE" => 'ly_role', //角色表名称(必配) "RBAC_USER_TABLE" => 'ly_role_user', //用户角色中间表名称(必配) "RBAC_ACCESS_TABLE" => 'ly_access', //权限表名称(必配) "RBAC_NODE_TABLE" => 'ly_node', //节点表名称(必配) );
注意:
• 以上有的配置项并非必须的,但标有“必配”,则必须配置
• 无需认证的权限和方法与需要认证的模块和动作可以根据需要选择性配置,还有部分权限相关配置并未列表出
RBAC处理类提供静态的方法
ThinkPHP的RBAC处理类提供给我们很多相关的静态方法如下:
方法名 | 接收参数说明 | 返回值 | 说明 |
RBAC::authenticate(map,map,model=''); |
$map:ThinkPHP数据库处理类的where条件参 数$model:用户表名,默认取配置文件C('USER_AUTH_MODEL') |
array |
RBAC::authenticate(array("username"=>"admin","userpwd" => I('POST.pwd','', 'md5'))); 返回值是在用户表中,以$map为条件where的查阅结果集 |
0RBAC::saveAccessList($authId=null); | $authId:用户识别号,默认取C('USER_AUTH_KEY'); | 返回一个空值 | 如果验证方式为登录验证,则将权限写入session中,否则不作任何处理 |
RBAC::getRecordAccessList(authId=null,authId=null,module=''); |
$authId:用户识别号(可不传) $module:当前操作的模块名称 |
Array | 返回一个包含权限的ID的数组 |
RBAC::checkAccess() | 无 | 返回true或false | 检查当前操作是否需要认证(根据配置中需要认证和不需要评论的模块或方法得出) |
RBAC::checkLogin() | 无 | true | 如果当前操作需要认证且用户没有登录,继续检测是否开启游客授权。如果开启游客授权,则写入游客权限;否则跳到登录页 |
RBAC::AccessDecision($appName=APP_NAME) | $appName:选传,有默认值 |
true:表示有操作权限 false:无操作权限 |
AccessDecision(appName=APPNAME)方法,检测当前项目模块操作,是否存在于appName=APPNAME)方法,检测当前项目模块操作,是否存在于_SESSION['_ACCESS_LIST']数组中$_SESSION['_ACCESS_LIST']['当前操作']['当前模块']['当前操作']是否存在。如果存在表示有权限,返回true;否则返回flase。 |
RBAC::getAccessList($authId) | $authId:用户识别号(选传,程序自动获取) | Array | 通过数据库查询取得当前认证号的所有权限列表 |
RBAC::getModuleAccessList(authId,authId,module) | $authId:用户识别号(必)$module:对应的模块(必) | Array | 返回指定用户可访问的节点权限数组 |
注意:在使用RBAC::AccessDecision()
方法时,如果你开启了项目分组,则必须传入当前分组,代码如下:
//权限验证 if(C('USER_AUTH_ON') && !$notAuth) { import('ORG.Util.RBAC'); //使用了项目分组,则必须引入GROUP_NAME RBAC::AccessDecision(GROUP_NAME) || $this->error("你没有对应的权限"); }
在完成用户登录,角色创建,节点增删改查的工作后,就只剩下了RBAC如何在对应程序代码中应用了。挻简单的,只用在原来的代码其他上改动几个地方即可。
• 用户登录时,写入用户权限
• 用户操作时,进行权限验证
下面是用户登录时的实现代码:
<?php namespace Home\Controller; use Think\Controller; use Org\Util\Rbac; class LoginController extends Controller { public function index() { $this->display(); } /* * 异步验证账号 */ public function checkUser() { $username = I('username'); $conditions = array('username' => ':username'); $result = M('User')->where($conditions)->bind(':username', $username)->find(); //如果不存在,则可以创建,也就是返回的是true if (!$result) { echo 'false'; } else { echo 'true'; } exit(); } /* * 异步验证密码 */ 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('非法访问'); // 采用htmlspecialchars方法对$_GET['name'] 进行过滤,如果不存在则返回空字符串 $username = I('post.username', '', 'htmlspecialchars'); // 采用正则表达式进行变量过滤,如果正则匹配不通过的话,则返回默认值。 //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('账号或密码错误',U('Home/Login/index')); if ($result['status'] == 0) return $this->error('该用户被锁定,暂时不可登录',U('Home/Login/index')); // 是否记住我的登录,设置一个Cookie,写在客户端 if (isset($_POST['remember'])) { $value = $result['id'] . '|' . get_client_ip() . '|' . $result['username']; $value = encrytion($value, 1); @setcookie('remember', $value, C('AUTO_LOGIN_LIFETIME'), '/'); } // 每天登录增加经验值 $today = strtotime(date('Y-m-d')); // 获取今天0时0分0秒的时间 // 如果上次的登录时间小于今天的时间,则增加经验值 $where2 = array('id' => $result['id']); if ($result['logintime'] < $today) { $user->where($where2)->setInc('expire', 10); } //更新登录户登录信息 $data_arr = array( 'id' => $result['id'], 'logintime' => time(), 'loginip' => get_client_ip(), ); if ($user->save($data_arr)) { // 获取$_SESSION['user_id'] 如果不存在则默认为0 session('uid', 0); session('username', $result['username']); session('loginAccount', $result['username']); session('loginUserName', $result['username']); session('uid', $result['id']); //RBAC 开始,用户认证SESSION标记 ,默认为"authId" session(C('USER_AUTH_KEY'), $result['id']); //如果为超级管理员,则无需验证 if ($_SESSION['username'] == C('RBAC_SUPERADMIN')) session(C('ADMIN_AUTH_KEY'), true); //用于检测用户权限的方法,并保存到Session中,读取用户权限 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生成规则 */ $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'); } }
接着在控制器目录创建一个CommonAction.class.php
文件,然后改写所有要权限验证的类,让其继承自CommonAction
。代码如下:
<?php namespace Home\Controller; use Think\Controller; use Org\Util\Rbac; class BaseController extends Controller { // /** // * ThikPHP自动运行方法,每一次,都会自动运行这个方法 // * 要判断一个session值是否已经设置,可以使用 // session('?name'); 相当于:isset($_SESSION['name']); // */ public function _initialize(){ /***************************************网站开关****************************************************/ if(!C('WEB_STATE')) exit('网站维护中'); /***********************************没有登录时候时需要执行的代码**************************************/ if(!isset($_SESSION[C('USER_AUTH_KEY')])) return $this->redirect('Login/index'); /***************************************自动登录配置************************************************/ 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]); } } /***************************************权限认证****************************************************/ $Public = in_array(MODULE_NAME,explode(',',C('NOT_AUTH_MODULE'))) || in_array(ACTION_NAME,explode(',',C('NOT_AUTH_ACTION'))); // 如果不在公共模块之中,同时开启权限验证的话,则开始认证过程 if(C('USER_AUTH_ON') && !$Public) { if(!Rbac::AccessDecision()) //通过accessDecision获取权限信息,true:表示有操作权限,false:无操作权限 { return $this->error("你没有对应的权限"); //没有获取到权限信息时需要执行的代码 } } /***************************************导航栏菜单显示****************************************************/ /* * 思路: * 1.取出所有权限节点。 * 2.取出当前登录用户拥有的模块权限(取英文名称)和操作权限(取ID) * 3.对所有权限进行遍历,先匹配模块权限,不存在删除,存在则匹配操作权限 */ // 超级管理员登录 if(session(C('ADMIN_AUTH_KEY'))) { $menus = D('AdminNode')->where('level = 2')->relation(true)->order('sort desc')->select();//取出所有节点 }else{ /** * [1]取出所有的权限,是通过关联模型从数据库中获取的 */ $menus = D('AdminNode')->where('level = 2')->relation(true)->order('sort desc')->select(); $module = ''; //存放拥有的模块 $node_id = ''; //存放拥有的模块 /** * [2]获取当前用户的所有权限,这个是从RBAC中获取的 */ $access_list = $_SESSION['_ACCESS_LIST']; //当前用户所拥有的权限 foreach ($access_list as $key => $value) { foreach ($value as $key1 => $value1) { $module = $module.','.$key1; //字符串拼接模块名称 foreach ($value1 as $key2 => $value2) { $node_id = $node_id.','.$value2; //字符串拼操作id } } } /** * [3]去除没有权限的节点,通过所有权限和用户已经拥有的权限比较 */ 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
中提供了一个_initialize()
方法,是在类初始化就会执行的,也就是只要后面控制器继承自CommonAction
类,就会在作对应操作时,执行_initialize()
方法。
以上是ThinkPHP中RBAC權限帶選單列顯示與詳細權限操作的詳細內容。更多資訊請關注PHP中文網其他相關文章!