用户
 找回密码
 入住 CI 中国社区
搜索
查看: 22987|回复: 25
收起左侧

[程序 App] 基于CI的RBAC访问控制

  [复制链接]
发表于 2014-2-28 13:40:07 | 显示全部楼层 |阅读模式
本帖最后由 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的管理也是根据自身的这套架构来控制的,但是根本没人去再对其进行操作,每次显示在后台特别别扭,所以这里增加两个参数,可以使不想显示在后台的管理节点以及菜单显示。


PHP复制代码
 
$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增加

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()

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


ci_rbac.zip

605.4 KB, 下载次数: 1306

发表于 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
发表于 2014-8-8 11:57:12 | 显示全部楼层
以前也是用过TP的rabc,现在改用CI 了,先感谢楼主的分享~~
发表于 2014-11-24 10:23:52 | 显示全部楼层
CI中RBAC权限,有权限直接显示摸模板,应该怎么弄?
发表于 2014-3-4 12:39:47 | 显示全部楼层
过来支持下
发表于 2014-3-5 11:01:23 | 显示全部楼层
发表于 2014-3-7 13:15:59 | 显示全部楼层
支持下。
发表于 2014-3-19 09:21:41 | 显示全部楼层
支持下
发表于 2014-3-20 10:38:01 | 显示全部楼层
学习了 谢谢~
发表于 2014-3-26 23:54:35 | 显示全部楼层
学习了 谢谢~
发表于 2014-3-27 16:07:50 | 显示全部楼层
支持一下~
发表于 2014-5-24 10:24:50 | 显示全部楼层
学习了,支持LZ

本版积分规则