toryzen 发表于 2014-2-28 13:40:07

基于CI的RBAC访问控制

本帖最后由 toryzen 于 2014-4-24 16:21 编辑

接触CI一周,有些用法也不知道是不是合理..哪里不合理请高手明示~

公司内部使用ThinkPHP用了他们官方示例的RBAC,最近花时间根据CI的一些特性以及ThinkPHP RBAC的基本理念,用CI实现了一套,说是RBAC,其实不只是权限控制,导航菜单的定制以及RBAC的后台页面化管理都已经初步完工了,下面就来看看最初版本。整个RBAC基本上就是RBAC0的模型,甚至比他更简单,用到的CI钩子。先看一下RBAC的配置文件,这基本上就是这个的辅助功能了。关于rbac_manage_menu_hidden,rbac_manage_node_hidden这是在使用think的rbac时感觉别扭的地方,RBAC的管理也是根据自身的这套架构来控制的,但是根本没人去再对其进行操作,每次显示在后台特别别扭,所以这里增加两个参数,可以使不想显示在后台的管理节点以及菜单显示。

$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")


下面小讲一下代码和原理config/hooks.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'   => '',
);


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()
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()
$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[$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张表主要是左侧的菜单,各表之间的关系还是比较明了简洁的
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=MyISAMDEFAULT 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=MyISAMDEFAULT 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=MyISAMDEFAULT 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=MyISAMDEFAULT CHARSET=utf8 COMMENT='用户表' AUTO_INCREMENT=6 ;

OK,CI刚刚接触,可能有些地方使用不当,中间也有很多可以改进之处,以后有时间慢慢更新。 样式使用的bootstrap3.0,http://torblog-wordpress.stor.sinaapp.com/uploads/2014/02/RTX%E6%88%AA%E5%9B%BE%E6%9C%AA%E5%91%BD%E5%90%8D-300x166.jpg
压缩包下载:

文章还发在我的BLOG上:https://github.com/toryzen/CI_RBAC


xiong 发表于 2014-8-8 15:20:35

楼主下载的源码中发现一个小错误,application/hooks/rbac_hook.php 页面
//ACL重新赋权                        $ci_obj->rbac_model->get_acl($_SESSION[$ci_obj->config->item('rbac_auth_key')]["INFO"]["role_id"]);
这里的id 改为role_id

xiong 发表于 2014-8-8 11:57:12

以前也是用过TP的rabc,现在改用CI 了,先感谢楼主的分享~~

烟圈 发表于 2014-11-24 10:23:52

CI中RBAC权限,有权限直接显示摸模板,应该怎么弄?{:1_1:}

水木清华 发表于 2014-3-4 12:39:47

过来支持下

dayrui 发表于 2014-3-5 11:01:23

:victory::victory::victory:

tomclub 发表于 2014-3-7 13:15:59

支持下。

oh_yes 发表于 2014-3-19 09:21:41

支持下

颠木 发表于 2014-3-20 10:38:01

学习了 谢谢~

Hoverbird 发表于 2014-3-26 23:54:35

学习了 谢谢~

い微风ァ飘叶 发表于 2014-3-27 16:07:50

支持一下~

bob 发表于 2014-5-24 10:24:50

学习了,支持LZ

jjyycaocsh 发表于 2014-6-11 17:32:34

支持下
页: [1] 2 3
查看完整版本: 基于CI的RBAC访问控制