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等等,但是应用不是很广泛。
(好了,以上内容你未必看的懂,或者说没有耐心看,这是给自己整理的一些小内容,所以写的非常随意,谅解,还是希望对您有帮助)