基於CI的RBAC訪問控制

公司內部使用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

相關文章
相關標籤/搜索