公司內部使用ThinkPHP用了他們官方示例的RBAC,最近花時間根據CI的一些特性以及ThinkPHP RBAC的基本理念,用CI實現了一套,說是RBAC,其實不僅是權限控制,導航菜單的定製以及RBAC的後臺頁面化管理都已經初步完工了,下面就來看看最第一版本。php
整個RBAC基本上就是RBAC0的模型,甚至比他更簡單,用到的CI鉤子。node
先看一下RBAC的配置文件,這基本上就是這個的輔助功能了。關於rbac_manage_menu_hidden,rbac_manage_node_hidden這是在使用think的rbac時感受彆扭的地方,RBAC的管理也是根據自身的這套架構來控制的,可是根本沒人去再對其進行操做,每次顯示在後臺特別彆扭,因此這裏增長兩個參數,可使不想顯示在後臺的管理節點以及菜單顯示。git
PHP複製代碼github
$config['rbac_auth_on'] = TRUE; //是否開啓認證
$config['rbac_auth_type'] = '2'; //認證方式1,登陸認證;2,實時認證
$config['rbac_auth_key'] = 'MyAuth'; //SESSION標記
$config['rbac_auth_gateway'] = 'Index/login'; //默認認證網關
$config['rbac_default_index'] = 'manage/Role/index'; //成功登陸默認跳轉模塊
$config['rbac_manage_menu_hidden'] = array('後臺管理'); //後臺管理導航中不顯示的菜單
$config['rbac_manage_node_hidden'] = array('manage'); //後臺管理節點中不顯示的菜單
$config['rbac_notauth_dirc'] = array(''); //默認無需認證目錄array("public","manage")
數據庫
複製代碼bootstrap
下面小講一下代碼和原理數組
config/hooks.php增長session
PHP複製代碼架構
$hook['post_controller_constructor'] = array(
'class' => 'Rbac',
'function' => 'aoto_verify',
'filename' => 'rbac_hook.php',
'filepath' => 'hooks',
'params' => '',
);
$hook['display_override'] = array(
'class' => 'Rbac',
'function' => 'view_override',
'filename' => 'rbac_hook.php',
'filepath' => 'hooks',
'params' => '',
);
$hook['pre_system'] = array(
'class' => '',
'function' => 'session_start',
'filename' => '',
'filepath' => '',
'params' => '',
);
ide
複製代碼
post_controller_constructor在你的控制器實例化以後,任何方法調用以前調用權限檢測,display_override這個主要是方便顯示用的,關於最後的pre_system調用的session_start,整個驗證過程都要用到session,而我實在是沒有找到好地方調用,只能放在這裏了,不知道ci是否是有啥參數之類的能直接開啓?
下面是關於aoto_verify的驗證的方法,與ThinkPHP是相似的,Think是繼承Action本身寫的一個Action,之後全部的方法都再集成,既然有了CI的鉤子,就不須要那麼費勁了,ThinkPHP驗證的是分組/模塊/方法,在CI中驗證的是目錄/控制器/方法。爲了更方便的取到上面的數據,使用CI的get_instance獲取超級對象,而後調用$ci_obj->router就能夠了。
aoto_verify()
PHP複製代碼
public function aoto_verify(){
$ci_obj = &get_instance();
//目錄
$directory = substr($ci_obj->router->fetch_directory(),0,-1);
//控制器
$controller = $ci_obj->router->fetch_class();
//方法
$function = $ci_obj->router->fetch_method();
//echo "(".$directory."/".$controller."/".$function.")";
if($directory!=""){//當非主目錄
if($ci_obj->config->item('rbac_auth_on')){//開啓認證
if(!in_array($directory,$ci_obj->config->item('rbac_notauth_dirc'))){//須要驗證的目錄
//驗證是否登陸
if(!isset($_SESSION[$ci_obj->config->item('rbac_auth_key')]["INFO"]["id"])){
error_redirct($ci_obj->config->item('rbac_auth_gateway'),"請先登陸!");
die();
}
if($ci_obj->config->item('rbac_auth_type')==2){//若爲實時認證
$ci_obj->load->model("rbac_model");
//檢測用戶狀態
$STATUS = $ci_obj->rbac_model->check_user_by_id($_SESSION[$ci_obj->config->item('rbac_auth_key')]["INFO"]["id"]);
if($STATUS==FALSE){
error_redirct($this->config->item('rbac_auth_gateway'),$STATUS);
die();
}
//ACL從新賦權
$ci_obj->rbac_model->get_acl($_SESSION[$ci_obj->config->item('rbac_auth_key')]["INFO"]["id"]);
}
//驗證ACL權限
if(@!$_SESSION[$ci_obj->config->item('rbac_auth_key')]["ACL"][$directory][$controller][$function]){
error_redirct("","無權訪問此節點!(".$directory."/".$controller."/".$function.")");
die();
}
}
}
//已登陸且有權限,獲取左側菜單
if($ci_obj->config->item('rbac_auth_type')==2){//若爲實時認證
$ci_obj->get_menu = $this->get_menu();
}else{
if(isset($_SESSION[$ci_obj->config->item('rbac_auth_key')]["MENU"])){
$ci_obj->get_menu = $_SESSION[$ci_obj->config->item('rbac_auth_key')]["MENU"];
}else{
$_SESSION[$ci_obj->config->item('rbac_auth_key')]["MENU"] = $this->get_menu();
$ci_obj->get_menu = $_SESSION[$ci_obj->config->item('rbac_auth_key')]["MENU"];
}
}
//默認重寫View開
$ci_obj->view_override = TRUE;
}
}
複製代碼
PS:在這裏只對controllers中的二級目錄作了權限控制,一級沒有。在上述方法後,還有一句$this->get_menu(),這裏是獲取左側的導航菜單數據。
get_menu()
PHP複製代碼
$ci_obj = &get_instance();
$ci_obj->load->database();
$query = $ci_obj->db->query("SELECT rm.id,rm.title,rm.node_id,rm.p_id,rn.dirc,rn.cont,rn.func FROM rbac_menu rm left join rbac_node rn on rm.node_id = rn.id WHERE rm.status = 1 AND rm.p_id is NULL ORDER BY sort asc");
$menu_data = $query->result();
$i = 0;
while(count($menu_data)>0){
$id_list = "";
foreach($menu_data as $vo){
if($i==2){
$vo->p_p_id = $Tmp_menu[1][$vo->p_id]->p_id;
}
$Tmp_menu[$i][$vo->id] = $vo;
$id_list .= $vo->id.",";
}
$id_list = substr($id_list,0,-1);
$query = $ci_obj->db->query("SELECT rm.id,rm.title,rm.node_id,rm.p_id,rn.dirc,rn.cont,rn.func FROM rbac_menu rm left join rbac_node rn on rm.node_id = rn.id WHERE rm.status = 1 AND rm.p_id in (".$id_list.") ORDER BY sort asc");
$menu_data = $query->result();
$i++;
}
$j = 0;
//按權限進行展現
foreach($Tmp_menu as $vo){
foreach($vo as $cvo){
if(@$_SESSION[$ci_obj->config->item('rbac_auth_key')]["ACL"][$cvo->dirc][$cvo->cont][$cvo->func]||!$cvo->node_id){
if($j==0){
if(@$_SESSION[$ci_obj->config->item('rbac_auth_key')]["ACL"][$cvo->dirc][$cvo->cont][$cvo->func]){
$menu[$cvo->id]["shown"] = 1;
}
$menu[$cvo->id]["self"] = array("title"=>$cvo->title,"uri"=>$cvo->dirc?$cvo->dirc."/".$cvo->cont."/".$cvo->func:$cvo->cont."/".$cvo->func);
}elseif($j==1){
if(@$_SESSION[$ci_obj->config->item('rbac_auth_key')]["ACL"][$cvo->dirc][$cvo->cont][$cvo->func]){
$menu[$cvo->p_id]["shown"] = 1;
$menu[$cvo->p_id]["child"][$cvo->id]["shown"] = 1;
}
$menu[$cvo->p_id]["child"][$cvo->id]["self"] = array("title"=>$cvo->title,"uri"=>$cvo->dirc?$cvo->dirc."/".$cvo->cont."/".$cvo->func:$cvo->cont."/".$cvo->func);
}else{
if(@$_SESSION[$ci_obj->config->item('rbac_auth_key')]["ACL"][$cvo->dirc][$cvo->cont][$cvo->func]){
$menu[$cvo->p_p_id]["shown"] = 1;
$menu[$cvo->p_p_id]["child"][$cvo->p_id]["shown"] = 1;
$menu[$cvo->p_p_id]["child"][$cvo->p_id]["child"][$cvo->id]["shown"] = 1;
}
$menu[$cvo->p_p_id]["child"][$cvo->p_id]["child"][$cvo->id]["self"] =array("title"=>$cvo->title,"uri"=>$cvo->dirc?$cvo->dirc."/".$cvo->cont."/".$cvo->func:$cvo->cont."/".$cvo->func);
}
}
}
$j++;
}
return $menu;
複製代碼
關於這個方法其實就是數組的拼接以及是否顯示的驗證。
關於數據庫,一共5張表,4張表實現權限的控制,1張表主要是左側的菜單,各表之間的關係仍是比較明瞭簡潔的
SQL複製代碼
CREATE TABLE IF NOT EXISTS `rbac_auth` (
`node_id` INT(11) NOT NULL COMMENT '節點ID',
`role_id` INT(11) NOT NULL COMMENT '角色ID',
UNIQUE KEY `nid_rid` (`node_id`,`role_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='角色與節點對應表';
CREATE TABLE IF NOT EXISTS `rbac_menu` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`title` VARCHAR(20) NOT NULL COMMENT '導航名稱',
`node_id` INT(11) DEFAULT NULL COMMENT '節點ID',
`p_id` INT(11) DEFAULT NULL COMMENT '導航父id',
`sort` INT(11) NOT NULL DEFAULT '0' COMMENT '排序',
`status` INT(11) DEFAULT '1' COMMENT '狀態(1:正常,0:停用)',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='菜單表' AUTO_INCREMENT=20 ;
CREATE TABLE IF NOT EXISTS `rbac_node` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`dirc` VARCHAR(20) NOT NULL COMMENT '目錄',
`cont` VARCHAR(10) NOT NULL COMMENT '控制器',
`func` VARCHAR(10) NOT NULL COMMENT '方法',
`memo` VARCHAR(25) DEFAULT NULL COMMENT '備註',
`status` INT(11) NOT NULL DEFAULT '1' COMMENT '狀態(1:正常,0:停用)',
PRIMARY KEY (`id`),
UNIQUE KEY `d_c_f` (`dirc`,`cont`,`func`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='節點表' AUTO_INCREMENT=24 ;
CREATE TABLE IF NOT EXISTS `rbac_role` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`rolename` VARCHAR(25) NOT NULL COMMENT '角色名',
`status` INT(11) NOT NULL DEFAULT '1' COMMENT '狀態(1:正常,0停用)',
PRIMARY KEY (`id`),
UNIQUE KEY `rolename` (`rolename`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='角色表' AUTO_INCREMENT=4 ;
CREATE TABLE IF NOT EXISTS `rbac_user` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(20) NOT NULL COMMENT '用戶名',
`password` VARCHAR(32) NOT NULL COMMENT '密碼',
`nickname` VARCHAR(20) NOT NULL COMMENT '暱稱',
`email` VARCHAR(25) NOT NULL COMMENT 'Email',
`role_id` INT(11) DEFAULT NULL COMMENT '角色ID',
`status` INT(11) NOT NULL DEFAULT '1' COMMENT '狀態(1:正常,0:停用)',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `email` (`email`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='用戶表' AUTO_INCREMENT=6 ;
複製代碼
OK,CI剛剛接觸,可能有些地方使用不當,中間也有不少能夠改進之處,之後有時間慢慢更新。
樣式使用的bootstrap3.0,
壓縮包下載:
文章還發在個人BLOG上:https://github.com/toryzen/CI_RBAC