RBAC(Role-Based Access Control )基于角色的访问控制(TP3.23)

RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联。简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般都是多对多的关系。

理论上的知识就不啰嗦了,本文章将要记录我在实现RBAC权限分配的主要思路和代码,注意,代码是使用thinkPHP3.23 框架编写!

数据表设计

在进行RBAC权限功能实现时,需要创建 3 个数据表,分别是管理员表、角色表、权限表。

管理员表(admin)结构如下:

CREATE TABLE `admin` (
`id`  int(10) NOT NULL AUTO_INCREMENT ,
`user`  varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`password`  char(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`roleid`  int(10) NOT NULL COMMENT '所属角色' ,
PRIMARY KEY (`id`)
)
;

角色表结构如下:

CREATE TABLE `role` (
`id`  int(10) NOT NULL AUTO_INCREMENT ,
`rolename`  varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`priid`  int(10) NOT NULL COMMENT '对应权限的id' ,
PRIMARY KEY (`id`)
)
;

最后一个,权限表如下:

CREATE TABLE `privilege` (`id`  int(10) NOT NULL AUTO_INCREMENT ,
`priname`  varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`mname`  varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`cname`  varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`aname`  varchar(255) NOT NULL ,
`parentid`  int(10) NOT NULL ,
PRIMARY KEY (`id`)
)
;

上面创建的 3 个数据表即可实现 RABC 权限关系管理;与很多网上教程的不同是,我们把三者的关系统一到自身的表中,不必再另外创建专门处它们关系的数据表;

 

权限管理的实现

思路:

在登录时,通过验证登录账号密码,确认管理员的身份—–》通过管理员的 roleid 字段确认该管理员属于哪个角色,并获取该角色表的所有信息 ——-》通过角色表的 priid 字段确认该角色拥有哪些权限,并获取拥有权限的所有信息;

好,首先获取用户登陆的信息;

Login 控制器中写校验用户登陆的方法:

public function index () {
	$admin = D('admin');
		if ($admin->create()) {
			if ($admin->Login()) {
				$this->success("登录中...",U('Admin/index/index'));
			}else{
				$this->error("账号或密码错误啦",U('Admin/Login/index'));
			}
		}else{
			$this->error($admin->getError());
		}
	return;
	}
	$this->display();
}

在Module层实现验证:

public function login (){
	$password = $this->password;
	$where = array(
		'user'=>"$this->user"
	);
	$info = $this->where($where)->find();
	if ($info) {
		$id = $info['id'];
		if ($info['password']==md5($password)){
			session('id',$info['id']);
			session('user',$info['user']);
			$this->getPri($info['roleid']);
			return true;
	        }else{
			return false;
		}
	}else{
		return false;
	}
}

此时,获取到登录用户的所有信息并存入到 session 中。存入session值之后,执行一个 $this->getPri($roleid)方法获取角色和权限;

获取角色与权限

该方法才是实现 RABC 核心方法,该方法每一步将注释讲解:

public function getPri($roleid){
	$role = D('role');  //实例化 角色表
	$pri  =  D("privilege");   //实例化 权限表
	$role->field("rolename,priid")->find($roleid);   //根据传递过来的 $roleid 获取角色名称和拥有权限
	if ($role->priid) 
                //如果角色权限id存在,则根据这些ID获取权限表中相关的信息,注意是concat(mname,'/',cname,'/',aname) u ,是将字段信息格式化并赋予别名 u 中
		$pris = $pri->field("concat(mname,'/',cname,'/',aname) u")->where("id in ({$role->priid})")->select();
		$_pris = array();  //定义权限的空数组,用于存储 u 信息,以便实现权限功能。
		foreach ($pris as $key => $value) {
			$_pris[] = $value['u'];    //将格式化权限信息存储在$_pris
		}
		session('privilege',$_pris);  存储在session中;
	}
}

以上方法是获取到角色与角色的权限。但是没有实现角色与权限功能的对于实现,实现该功能也容易,只需要 公共的控制器中编写校验代码即可。

权限的实现

我们控制权限的实现是校验权限表中是否拥有 对于的 模块/控制器/方法 , 也即是数据表 privilege 的权限来着 mname/cname/aname  是否在代码中拥有对于的方法;

if (!in_array(MODULE_NAME.'/'.CONTROLLER_NAME.'/'.ACTION_NAME, session("privilege"))) {
	$this->error("你没有该权限操作哦!");
}

上面我们已经将在数据库中获取的 concat(mname,’/’,cname,’/’,aname) u 以数组的形式存入了session中,例如我们插入数据

insert into privilege (`mname`,`cname`,`aname`) values ('admin','index','index');

如果我们分配有该权限,即可访问 admin/index/index ,否则将提示:“你没有该权限操作哦!”;

到这里 RABC 的核心实现过程已经说完,但是,我们稍微升级一下,再添加一个需求,在后台页面中,只展示用户拥有的权限列表,也即是说我们需要隐藏用户未拥有的权限。

展示权限菜单

实现原理也简单,就是获取一级权限和二级权限作为我们的菜单即可,在 getPri 方法中修改如下:

public function getPri($roleid){
	$role = D('role');
	$pri  =  D("privilege");
	$role->field("rolename,priid")->find($roleid);
	if ($role->priid ) {
                 //获取更多的信息
		$pris = $pri->field("id,priname,parentid,mname,cname,aname,concat(mname,'/',cname,'/',aname) u")->where("id in ({$role->priid})")->select();
		$_pris = array();
		$menu = array();
		foreach ($pris as $key => $value) {
			$_pris[] = $value['u'];
			if ($value['parentid']==0) {  
				//获取一级权限
				$menu[] = $value;
			}
		}
		session('privilege',$_pris);
		//目的是使menu的parentid和pris的id关系获取二级权限
		foreach ($menu as $k => $v) {
			foreach ($pris as $k1 => $v1) {
				if ($v['id']==$v1['parentid']){
				$menu[$k]['sub'][] = $v1;
				}
			}
		}
		session('menu',$menu);
	}

打印某账号的 $menu 信息是一个如下的 四维数组:

array(1) {
  [0]=>
  array(8) {
    ["id"]=>
    string(1) "1"
    ["priname"]=>
    string(12) "常用操作"
    ["parentid"]=>
    string(1) "0"
    ["mname"]=>
    string(5) "Admin"
    ["cname"]=>
    string(5) "Admin"
    ["aname"]=>
    string(5) "Andex"
    ["u"]=>
    string(17) "Admin/Admin/Andex"
    ["sub"]=>
    array(1) {
      [0]=>
      array(7) {
        ["id"]=>
        string(2) "12"
        ["priname"]=>
        string(12) "友情链接"
        ["parentid"]=>
        string(1) "1"
        ["mname"]=>
        string(5) "Admin"
        ["cname"]=>
        string(4) "Link"
        ["aname"]=>
        string(3) "lst"
        ["u"]=>
        string(14) "Admin/Link/lst"
      }
    }
  }
}

在展示菜单栏目中使用两次 foreach 循环即可展示权限菜单:

      <ul class="sidebar-list">
             
            $menu = session("menu");
            foreach($menu as $k=>$v): ?>
                <li>
                    <a href="#"><i class="icon-font">i> echo $v['priname'] ?>a>
                    <ul class="sub-menu">
                         foreach($v['sub'] as $k1=>$v1): ?>
                        <li><a href=" echo U($v1['mname'].'/'.$v1['cname'].'/'.$v1['aname']); ?>"><i class="icon-font">i> echo $v1['priname'] ?>a>li>
                         endforeach; ?>
                    ul>
                li>
             endforeach; ?>
            ul>

有一个思考,为什么不给用户直接赋予权限呢?为什么要引入一个角色?在前几年的确流行 基于用户的权限控制,即ACL:Access Control List,但是 RBAC 的核心是用户只和角色关联,而角色代表对了权限,这样设计的优势在于使得对用户而言,只需角色即可以,而某角色可以拥有各种各样的权限并可继承。ACL和RBAC相比缺点在于由于用户和权限直接挂钩,导致在授予时的复杂性,虽然可以利用组来简化这个复杂性,但仍然会导致系统不好理解,而且在取出判断用户是否有该权限时比较的困难,一定程度上影响了效率。

除两上述两种主要的模型之外,还有包括:基于属性的访问控制ABAC和基于策略的访问控制PBAC等等,但是应用不是很广泛。

(好了,以上内容你未必看的懂,或者说没有耐心看,这是给自己整理的一些小内容,所以写的非常随意,谅解,还是希望对您有帮助)

你可能感兴趣的:(PHP)