ThinkPHP框架使用心得三 RBAC权限控制
RBAC英文全称Role-Based Access Control。即基于角色的权限控制。它的权限控制原理是将个项目-模块-操作的使用权限分配给角色组。然后将用户分到各用户组中,当然,一个用户可能有多个用户组。这样用户从属于用户组后就具有了该用户组的相关操作权限。ThinkPHP中有一个封装的很好的RBAC类库,十分好用的说。同时官方也提供的demo包中也有rbac的例子,不过,据说好多人看着头疼。我之前也拿这个例子来看,发现。。。超越了我的理解啊。。。。现在我对rbac的原理大体了解后来看它的demo,我知道问题所在了,它的demo写的太好。对于我这种刚学习的人而言,绝大部分的精力会不由自主的放进他的操作流程,而会忽略最重要的数据表之间的操作,从而很可能难以理解rbac的原理。
我自己写了一个rbac的demo,最初的工作是利用curd编写一个对文章的增改查的程序,连用户登录都免了,简要代码:
class IndexAction extends Action {
public function index() {} //文章列表
public function add() {} //新增文章
public function edit() {} //文章编辑
public function view() {} //文章查看
}
实现的效果:

一个简单的系统搭建完成了,下面加入权限控制。
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那样便捷的操作界面。
附源代码,希望大神指正。
浙公网安备 33010602011771号