系统权限设计中RBAC模型的使用

什么是RBAC

RBAC,全称是Role-Based-Access-Control,可以译为基于角色的权限控制,是一种应用非常广泛的权限控制模型。

什么是角色(Role)

角色不是一个用户实体,而是代表了一组行为能力或责任的命名实体,以我们常用的QQ群为例,有以下角色:超级管理员、群主、管理员、普通成员、非群成员。

每个角色都有不同的权限:

  • 超级管理员可以管理任何的QQ群,但是一般只能禁言、删除群,不能管理具体的某个群成员,也不能在群里聊天;
  • 群主可以管理自己的QQ群,可以在群里聊天;
  • 管理员可以管理自己的QQ群,可以聊天,但是不能解散群,也不能任命或撤销其他管理员;
  • 普通成员则没有管理权限,只有在群里聊天的权限
  • 非群成员无管理权限,也无法在群里聊天。

所以可以知道,每个角色都对应着一组行为能力或责任。

隐式的基于角色的权限控制

对于QQ群的例子来说。如果我们要做一个删除某个群成员的动作,伪代码可能如下:

if( user.hasRole('Group Owner') || user.hasRole('Group Manager') ){
    // delete the Group Member
} else {
    // Permission Denied
}

那么现在,如果腾讯的权限策略发生了改变,超级管理员也可以删除某个群的群成员来防止某些不当言论的传播,那么代码就要改为

if( user.hasRole('Group Owner') || user.hasRole('Group Manager') || user.hasRole('Super Manager') ){
    // delete the Group Member
} else {
    // Permission Denied
}

上面这种权限的管理方式,就可以称为隐式的基于角色的权限控制。因为Group OwnerGroup ManagerSuper Manager并不能显式的表达出:它们的角色具有删除群成员的权限。没有任何的代码显式的定义了这些角色的权限。我们只能从代码中隐式的推测出:这三个角色拥有删除群成员的权限。所以程序员们使用if/else语句来反映这些假设。

显式的基于角色的权限控制

了解了隐式的基于角色的权限控制,那么我们就可以知道,显式的基于角色的权限控制要有能力直接表达出:当前用户有权限去删除群成员。这样我们的代码可以调整如下:

if( user.isPermitted('GroupMember:delete:478') ){
    // delete the Group Member
} else {
    // Permission Denied
}

这样从代码中可以看到,如果当前用户被允许删除ID为478的群成员,那么就去删除,否则报权限不足的错误。至于isPermitted()中如何去判断权限的,可能依然是回到了哪些角色有哪些权限的问题。但是不同之处在于,应对上面的需求变更问题时,我们只需要更改user的isPermitted的判断规则,而不用去更改散布在代码中各个地方的if语句。可以做到以最小的变更来应对复杂的需求变化。

隐式 vs 显式

隐式和显式在我看来,其内在的权限控制仍然是一样的,都是基于角色在做权限判断。但对外的抽象则是截然不同的:隐式侧重于某个用户是否有某些角色,显式则直接将问题聚焦于某个用户是否有对某个资源的某个操作的权限。这在写代码中给程序员带来的影响是不一样的。截取一段项目中的代码来佐证:

//检查操作的权限
if(
    !$familyDB->isUserForFamily($familyId,$userId)&&
    !$familyDB->isAdminForFamily($familyId,$userId)&&
    !$familyDB->isOriginatorForFamily($familyId,$userId)
){
    Util::printResult( $GLOBALS['ERROR_PERMISSION'], "操作权限错误");
    exit;
}

这段代码判断了用户是否对家族有读取权限。因为我们是隐式的基于角色的权限控制,很直观的想法就是:家族成员、家族管理员、家族创始人这三个角色都有对家族的读取权限。所以这里判断了三个权限。事实上,家族创始人的判断是多余的,因为家族创始人肯定属于家族成员。但是真正在写代码的时候,很有可能考虑不到这个问题。

但如果是显式的基于角色的权限控制,这个if语句就是:

//检查操作的权限
if(
    !$familyDB->isUserHasReadPermission($familyId,$userId))
){
    Util::printResult( $GLOBALS['ERROR_PERMISSION'], "操作权限错误");
    exit;
}

程序员写代码时就不会做出多余的判断。可以得出,显式的抽象表达能力是明显更强的。

新的RBAC:Resource-Based-Access-Control

通过对比隐式显式的区别,我们知道显式是直接检查某个用户对某个资源(Resource)是否有某个权限。所以不如直接抛弃角色(Role)的概念,将资源这个概念引入。这样就有了基于资源的权限控制(Resource-Based-Access-Control)

xdebug的简易使用教程

1.ubuntu下的安装

通过 pecl 安装

pecl install xdebug

然后将xdebug.so加入php.ini中,注意如果使用的是fpm,则需要加入到fpm下的php.ini,同理cli环境下则需要向cli下的php.ini添加

zend_extension=”/usr/local/php/modules/xdebug.so”

注意:xdebug是zend的拓展,不需要添加extension=xdebug.so

通过编译安装

git clone git://github.com/xdebug/xdebug.git
cd xdebug
phpize
./configure –enable-xdebug
make
make install

2.配置php使用xdebug

xdebug有许多特性。

2.1 通过设置来影响var_dump()

影响var_dump()的属性有:

  • xdebug.var_display_max_children,
  • xdebug.var_display_max_data
  • xdebug.var_display_max_depth

这三个属性的值都是数字类型的。会影响var_dump()函数显示的变量的内容长度和深度。

可以在php.ini做以下设置:

xdebug.var_display_max_depth = 2
xdebug.var_display_max_data = 8
xdebug.var_display_max_children = 3

具体的值可以自己手动调整

另外还有xdebug.cli_colorxdebug.overload_var_dump会影响到显示的效果

2.2 堆栈跟踪

演示脚本如下:

<?php
//这个脚本会超时
function foo( $a ) {
    for ($i = 1; $i < $a['foo']; $i++) {
        if ($i == 500000) xdebug_break();
    }
}

set_time_limit(1);
$c = new stdClass;
$c->bar = 100;
$a = array(
    42 => false, 'foo' => 9121240000000000,
    $c, new stdClass, fopen( '/etc/passwd', 'r' )
);
foo( $a );
?>

我们使用设置

xdebug.collect_params = 1

结果如下:
params为1

修改一下设置:

xdebug.collect_params = 3

params为3

可以看到浏览器中的报错信息更多了,体现在foo()函数中的参数数量

同样的,我们还可以设置

xdebug.dump_globals = On
xdebug.dump.SERVER = ‘REQUEST_URI’

这样则可以展示一些超全局变量。这里指定了请求的URI

超全局变量

还可以设置

xdebug.show_local_vars = On

来展示程序运行期间的本地变量
本地变量

2.3 函数跟踪

使用xdebug可以记录所有的函数调用。

测试脚本如下:

<?php

ini_set('xdebug.trace_format','0');

xdebug_start_trace();
$str = "Xdebug";
function ret_ord( $c )
{
    return ord( $c );
}

foreach ( str_split( $str ) as $char )
{
    echo $char, ": ", ret_ord( $char ), "\n";
}
xdebug_stop_trace();
?>

使用的设置如下:

;代码跟踪日志文件位置,注意要先新建这个/tmp/php_traces/fpm目录,并设置777
xdebug.auto_trace = Off
xdebug.trace_output_dir = /tmp/php_traces/fpm
;代码跟踪日志文件格式 
xdebug.trace_output_name = trace.%c.%p
;trace中显示函数的参数值,这个很有用,待会细说
xdebug.collect_params = 3
xdebug.collect_includes = On
xdebug.collect_return = On
xdebug.show_mem_delta = On
xdebug.var_display_max_depth = 2

结果如下:
此处输入图片的描述

通过调整xdebug.trace_format的值可以更改记录的格式。0是人类可读,1是机器可读,2是html

2.4远程调试

这里xdebug中非常好用的一个功能。通过设置,我们可以在IDE中单步调试。下面我会使用vscode来演示一遍。

2.4.1 环境准备

1.首先我们需要为vscode安装xdebug的插件。

2.配置好调试环境

此处输入图片的描述

这一步是在vscode左侧调试栏新增配置,然后选择php即可。

3.打上断点,启动调试

此处输入图片的描述

4.在浏览器中访问这个页面即可

此处输入图片的描述

参考链接

https://xdebug.org/docs/all#default