最近有点小忙,好久没有学习了。。。我忏悔。。。。
RBAC英文全称Role-Based Access Control。即基于角色的权限控制。它的权限控制原理是将个项目-模块-操作的使用权限分配给角色组。然后将用户分到各用户组中,当然,一个用户可能有多个用户组。这样用户从属于用户组后就具有了该用户组的相关操作权限。ThinkPHP中有一个封装的很好的RBAC类库,十分好用的说。同时官方也提供的demo包中也有rbac的例子,不过,据说好多人看着头疼。我之前也拿这个例子来看,发现。。。超越了我的理解啊。。。。现在我对rbac的原理大体了解后来看它的demo,我知道问题所在了,它的demo写的太好。对于我这种刚学习的人而言,绝大部分的精力会不由自主的放进他的操作流程,而会忽略最重要的数据表之间的操作,从而很可能难以理解rbac的原理。
我自己写了一个rbac的demo,最初的工作是利用curd编写一个对文章的增改查的程序,连用户登录都免了,简要代码:
1 class IndexAction extends Action { 2 public function index() {} //文章列表 3 public function add() {} //新增文章 4 public function edit() {} //文章编辑 5 public function view() {} //文章查看 6 }
实现的效果:
一个简单的系统搭建完成了,下面加入权限控制。
RBAC的实现至少需要五张数据表,(我使用tp默认的前缀think_)分别为:
字段 | 类型 | 说明 |
id | int(11) | pk |
username | varchar(50) | |
password | varchar(50) |
字段 | 类型 | 说明 |
role_id | smallint(6) | 角色id |
node_id | smallint(6) | 节点id |
level | tinyint | 表示所属层次,项目=〉1,模块=〉2,操作=〉3 |
module | varchar(50) |
字段 | 类型 | 说明 |
id | smallint(6) | pk |
name | varchar(20) | |
title | varchar(50) | |
status | tinyint | |
remark | varchar(255) | |
sort | smallint(6) | |
pid | smallint(6) | |
level | tinyint |
字段 | 类型 | 说明 |
id | smallint(6) | pk |
name | varchar(20) | |
pid | smallint(6) | |
status | tinyint | |
remark | varchar(255) |
字段 | 类型 | 说明 |
role_id | smallint(6) | |
user_id | smallint(6) |
RBAC认证流程图
rbac的原理其实就是这几张表的数据逻辑关系。我是手动建立相关数据,一遍数据建立下来,逻辑理清了,基本原理也能理解的不差了。
(一)建立用户表数据
建立三个用户,分别为system,admin,user1
(二)建立角色组
建立两个角色组,分别为admin,user,即管理组和普通组
(三)建立用户和用户组的关系
将system,admin划归角色组1
将user1划归角色组2
这样组合用户的对应关系就建立好了
(4)建立节点表
所谓节点就是所有的项目、模块、操作的列表。这张表应该算rbac的核心。
我的demo中项目名rabc,有一个模块即IndexAction,下面还有index,add,edit,view四个操作。将这6条分别计入这张表中,注意pid的设置。同时level的值,1、2、3分别代表项目,模块,操作。
(5)建立权限表
对于权限的分配就在access表中。
表中数据的意思是role_id=1的组,即admin组具有操作节点1,2,3,4,6的权限,不具备操作节点5即rbac-Index-edit的权限。role_id=2的组即用户组织具有节点123的操作权限。
很重要的一点就是,权限的安排需要按层次来,即只有具备对项目的操作权限才能操作模块,同理,只有具备对项目-模块的操作权限才能操作下面的方法。
这样,rbac的操作从数据表层面来讲已经完成。下面对代码进行修改以完成rbac的权限控制。
首先,将RBAC.class.php复制到项目目录Lib\Org下,也可以直接使用系统目录Lib\ORG\Util下的类库文件,只要能够import即可。
然后,在项目配置文件中定义rbac的相关配置项:
//rbac配置项 'USER_AUTH_ON' =>true, 'USER_AUTH_TYPE' =>2, // 默认认证类型 1 登录认证 2 实时认证 'USER_AUTH_KEY' =>'authId', // 用户认证SESSION标记 'ADMIN_AUTH_KEY' =>'administrator', 'USER_AUTH_MODEL' =>'User', // 默认验证数据表模型 'AUTH_PWD_ENCODER' =>'md5', // 用户认证密码加密方式 'USER_AUTH_GATEWAY' =>'/Public/login',// 默认认证网关 'NOT_AUTH_MODULE' =>'Public', // 默认无需认证模块 'REQUIRE_AUTH_MODULE' =>'', // 默认需要认证模块 'NOT_AUTH_ACTION' =>'', // 默认无需认证操作 'REQUIRE_AUTH_ACTION' =>'', // 默认需要认证操作 'GUEST_AUTH_ON' =>false, // 是否开启游客授权访问 'GUEST_AUTH_ID' =>0, // 游客的用户ID 'SHOW_RUN_TIME' =>true, // 运行时间显示 'SHOW_ADV_TIME' =>true, // 显示详细的运行时间 'SHOW_DB_TIMES' =>true, // 显示数据库查询和写入次数 'SHOW_CACHE_TIMES' =>true, // 显示缓存操作次数 'SHOW_USE_MEM' =>true, // 显示内存开销 'DB_LIKE_FIELDS' =>'title|remark', 'RBAC_ROLE_TABLE' =>'think_role', 'RBAC_USER_TABLE' =>'think_role_user', 'RBAC_ACCESS_TABLE' =>'think_access', 'RBAC_NODE_TABLE' =>'think_node',
这些配置项可以直接从tp的rbacdemo中复制。每项的意思也基本都注明了。不需要说太多了。
再然后定义一个PublicAction用来放置一些不需要进行认证的模块。如果修改了配置项中NOT_AUTH_MODULE,那么就建立相应名称的action。这里面放置登录,登出,检验登录情况等操作。如果用户连登录都发现没有权限,这是个多么疯狂的情况,用户想登录一直说:你丫无权操作,一边凉快去。多抓狂。
代码结构:
class PublicAction extends Action{ // 用户登录页面 public function login() { if (!isset($_SESSION[C('USER_AUTH_KEY')])) { $this->display(); } else { $this->redirect('Index/index'); } } // 登录检测 public function checkLogin() { } function loginout() { if (isset($_SESSION[C('USER_AUTH_KEY')])) { unset($_SESSION[C('USER_AUTH_KEY')]); unset($_SESSION); session_destroy(); $this->assign("jumpUrl", __URL__ . '/login/'); $this->success('登出成功!'); } else { $this->error('已经登出!'); } } }
login,logout就是判断session时候存在。存在就认为已经登录,执行页面跳转或者删掉这个session以达到退出效果。
checklogin()是rbac的具体实现
public function checkLogin() { if (empty($_POST['username'])) { $this->error('帐号错误!'); } elseif (empty($_POST['password'])) { $this->error('密码必须!'); } //生成认证条件 $map = array(); // 支持使用绑定帐号登录 $map['username'] = $_POST['username']; import('ORG.Util.RBAC'); $authInfo = RBAC::authenticate($map); //使用用户名、密码和状态的方式进行认证 if (false === $authInfo) { $this->error('帐号不存在或已禁用!'); } else { if ($authInfo['password'] != md5($_POST['password'])) { $this->error('密码错误!'); } $_SESSION[C('USER_AUTH_KEY')] = $authInfo['id']; if ($authInfo['username'] == 'system') { $_SESSION['administrator'] = true; } // 缓存访问权限 RBAC::saveAccessList(); $this->success('登录成功!'); } }
首先判断提交过来的表单信息,然后引入rbac类,然后调用RBAC::authenticate($map);来获取认证信息。然后根据认证信息来判断用户时候登录成功以及具有的权限。
再然后编写一个BaseAction.class.php,这里放置一个自动方法,就是每次执行这个action时,这个方法会像构造函数一样自动执行。这个方法就是检查用户权限。
function _initialize() { // 用户权限检查 if (C ( 'USER_AUTH_ON' ) && !in_array(MODULE_NAME,explode(',',C('NOT_AUTH_MODULE')))) { import ( '@.Org.RBAC' ); if (! RBAC::AccessDecision ()) { //检查认证识别号 if (! $_SESSION [C ( 'USER_AUTH_KEY' )]) { //跳转到认证网关 redirect ( PHP_FILE . C ( 'USER_AUTH_GATEWAY' ) ); } // 没有权限 抛出错误 if (C ( 'RBAC_ERROR_PAGE' )) { // 定义权限错误页面 redirect ( C ( 'RBAC_ERROR_PAGE' ) ); } else { if (C ( 'GUEST_AUTH_ON' )) { $this->assign ( 'jumpUrl', PHP_FILE . C ( 'USER_AUTH_GATEWAY' ) ); } // 提示错误信息 $this->error ( L ( '_VALID_ACCESS_' ) ); } } } }
然后修改IndexAction使它由Action改为继承至BaseAction,这样每个页面的执行都会执行自动方法。以后如果增加新的模块也让它继承BaseAction这样就实现了权限控制了。
以上简单的权限控制就完成了。代码基本可以从官方例子中复制稍作修改即可。下面看看效果。
结果不太好表示,按之前对表的编辑,结合实际调试,使用system登录时,虽然他属于admin组,而admin组并不具备编辑的权限,但是由于它是系统管理员,判定具有所有权限。用admin登录可以发现只有编辑链接点击时无权操作。使用user1登录,除了能看首页,其他什么事不能干。
就此,RBAC算是结束了。至于官方的demo,是可以再页面上配置各种值,说白了就是将我的手动编辑表的过程在界面上实现。能够理解原理,下面就可以试着做出官方demo那样便捷的操作界面。
附源代码,希望大神指正。