基于角色的权限控制 - RBAC

时间:2021-03-18 15:25:44

实现步骤:

一 、数据库字段设计:

大体可分为三个数据表(权限表,角色表 和 管理员表),在角色表中有权限id列表字段,这个字段可再细分(也就是说可以和管理员表的id字段组成另一个表)。

权限表字段包括:id,权限名称,模块名称,控制器名称,方法名称,父级id,level字段(为了实现树状结构),全路径字段(和id连接,实现相同父级的id排序一起)。

角色表字段包括:id,角色名称 和 角色拥有的权限列表。

管理员表字段包括:id,管理员名称,管理员密码,和角色id ,加密密钥(salt)。

注:字段的设计类型选择最小类型。所有字段都加上not null ,可为空则default设置。给搜索频繁的字段加上索引。并给每个字段和表都注释。


二、在权限模块中,获取树状结构数据

//调用tp中 $_validate 方法 对表单进行验证
protected $_validate = array(
array('pri_name','require','权限名称不能为空!'),
array('module_name','require','模块名称不能为空'),
array('controller_name','require','控制器名不能为空'),
array('action_name','require','方法名不能为空'),
);

//获取树形结果的数据
public function getPriTree($pri_level = 2)
{
$sql = 'SELECT * FROM sh_privilege WHERE pri_level <= '.$pri_level.' ORDER BY CONCAT(pri_path,"-",id) ASC';

return $this->query($sql);

}

//添加插入数据时的,前置钩子
public function _before_insert(&$data,$option)
{
//对pri_level和pri_path 数据的拼装
//pri_level 的值 是上个pri_level值+1 ,pri_path 的值为上父级的pri_path+id,若是为*则pri_level和pri_path都为0
if($data['parent_id'] != 0)
{
//取出上级父id 的 pri_level 和 pri_path
$sql = "SELECT pri_level,pri_path FROM sh_privilege WHERE id =".$data['parent_id'] ;
$pdata = $this->query($sql);

//拼装pri_level 和pri_path,使用数据对象收集这两个数据
$data['pri_level'] = $pdata[0]['pri_level'] + 1;
$data['pri_path'] = $pdata[0]['pri_path'] .'-'.$data['parent_id'];
}

}

三、在角色控制器中,设置显示权限列表

//展示权限列表
public function lst(){
$Model = D('Role');
$data = $Model->field('a.*,GROUP_CONCAT(b.pri_name) pri_name')->alias('a')->join('LEFT JOIN sh_privilege b ON FIND_IN_SET(b.id , a.pri_id_list)')->group('a.id')->select();

$this->assign('data',$data);
$this->display();
}

角色模型设计

//调用tp中 $_validate 方法 对表单进行验证
protected $_validate = array(
array('role_name','require',"角色名称不能为空"),
array('pri_id_list','require',"角色所拥有权限不能为空",2),

);


//添加一个前置添加钩子,在插入数据时,处理权限id值
protected function _before_insert(&$data,$option)
{
//把数组形式的ids转为字符串型的ids
$pri_id_list = implode(',' , $data['pri_id_list']);
//用$data数据对象收集
$data['pri_id_list'] = $pri_id_list;
}

//添加一个pudate前置钩子,处理pri_id_list数组变为字符串形式
protected function _before_update(&$data,$option)
{
$data['pri_id_list'] = implode(',', $data['pri_id_list']);
//$option 参数里有表名,模型名称,和id

}

管理员模型设计

//调用tp中 $_validate 方法 对表单进行验证
protected $_validate = array(

array('admin_name','require',"管理员名称不能为空"),
array('admin_pass','require',"密码不能为空",1,'regex',1),//必须验证,是在添加数据时验证

/************ 以下四项在登录时不会验证 ******************/

array('radmin_pass','admin_pass',"确认密码不一致",2,'confirm'),//编辑数据时验证

array('role_id',0,"角色未选择",1,'notequal',1),//表达的意思是不等于0就通过验证,等于0就提示错误信息。

array('admin_name','',"管理员名称已经存在",1,'unique',2),


//登陆时,验证码验证
array('admin_pass','require','密码不能为空!',1,'regex',4),
array('chk_code','chk_code','验证码错误',1,'callback',4),

);


//验证验证码是否正确
protected function chk_code($data)
{
$verify = new \Think\Verify();
return $verify->check($data);
}

//定义添加数据时的前置钩子
protected function _before_insert(&$data,$options)
{
$salt = substr(uniqid(),-6);
$data['admin_pass'] = md5(md5($_POST['admin_pass']).$salt);
$data['salt'] = $salt;
}


// 登录
public function login()
{
//检查用户名
$admin_name = $_POST['admin_name'];
$user = $this->where("admin_name ='$admin_name' ")->find();//$this->admin,用到数据对象调用属性

// var_dump($user);die;
if($user)
{

//验证密码情况
if(md5(md5($_POST['admin_pass']).$user['salt']) == $user['admin_pass'])
{

//成功,则把信息存入session
session('uid',$user['id']);
session('uname',$user['admin_name']);
//并取出管理员权限保存到session中
$this->_loadPriDataToSession($user['role_id']);
return true;
}
else

return 2;
}
else
return 3;//用户不存在;
}


//定义函数取出登陆后管理员权限,保存到session中
protected function _loadPriDataToSession($id)
{

if($id != 1)
{
//实例化角色表,取出所有权限id,
$roleModel = D('Role');
$roleinfo = $roleModel->find($id);
$pri_id_list = $roleinfo['pri_id_list'];// 1,2,3,4,5,6
//实例化权限对象
$model = D('Privilege');
$priData = $this->query("SELECT * FROM sh_privilege WHERE id IN ($pri_id_list) ORDER BY CONCAT(pri_path,'-',id) ASC ");
}
else
{
//如果是超级管理员,就取出所有的权限
$priData = $this->query("SELECT * FROM sh_privilege ORDER BY CONCAT(pri_path,'-',id) ASC") ;
}


//收集模块名称/控制器/方法
$_url = array();
$_menu = array();
$_menu1 = array();
foreach($priData as $k=>$v)
{
$_url[] = $v['module_name'].'/'. $v['controller_name'].'/'.$v['action_name'];
if($v['pri_level'] <=1 )
{
$_menu[] = $v;
}

}

foreach($_menu as $k=>$v)
{
$_menu1[$v['parent_id']][] = $v;
}
session('url',$_url);
session('menu',$_menu1);

// var_dump($_menu);die;

}


public function logout()
{
$se = session(null);

}


最后,再新建个控制器,让权限控制,角色控制,管理员控制器,展示后台控制器,都继承该控制器。在该控制器内,实现构造方法,对除了展示后台登陆外的所有权限进行条件限制(根据管理员登陆的信息角色id 查的 其拥有的权限,并展示其拥有的后台按钮)。

public function __construct()
{// 如果有父类必须先调用父类的构造函数
parent::__construct();

if(!session('uid'))
{
// redirect(U('Login/login'));
$this->error('无权访问!请先登陆!',U('Login/login'));
}
else
{
//因为Privilege/Layout 在session用户信息中没有,所欲单独判断。
if(MODULE_NAME.'/'.CONTROLLER_NAME == 'Privilege/Layout')
return true;
// redirect(U('Layout/index'));//不能再有跳转、已经和符合条件要跳转的相冲突,最后被浏览器给拦截下来!
}

$url = session('url');
// echo '<pre>';
// var_dump(MODULE_NAME.'/'.CONTROLLER_NAME.'/'.ACTION_NAME);
// var_dump($url);die;
if(!in_array(MODULE_NAME.'/'.CONTROLLER_NAME.'/'.ACTION_NAME,$url))
{
$this->error('无权访问!请先登陆!',U('Login/login'));
}


}