理解yii的授权等级检查

原文: http://www.yiiframework.com/wiki/136/getting-to-understand-hierarchical-rbac-scheme/

凑合着看…….

 

Yii 1.1: 理解继承体系的RBAC模式
‘验证与授权’ 是一个很好的教程. 在其他话题上, 它描述了Yii中RBAC的基本实现. 然而无论如何努力的阅读教程,我不能准确的理解继承是怎样工作的. 我学会了怎么定义验证体系, 业务规则是怎样执行的,怎么配置authManager, 但是不知道如何建立自己的继续体系,结点的验证检查顺序是怎样的,检查何时终止,检查的结果是怎么样?

除了深入Yii的源代码我别无他法,我将把这些发现写在这篇文章中. 需要提到的是Yii的代码一点也不难, 它的结构很简单, 很好理解. 然而, 接下来的内容可以节约你的时间.

你将会更多容易理解这篇文章如果你已经熟悉了上边提到的文章,尤其是’Role-Based Access Control’

我们来看一下文章中的继承等级例子:

$auth=Yii::app()->authManager;

$auth->createOperation('createPost','create a post');
$auth->createOperation('readPost','read a post');
$auth->createOperation('updatePost','update a post');
$auth->createOperation('deletePost','delete a post');

$bizRule='return Yii::app()->user->id==$params["post"]->authID;';
$task=$auth->createTask('updateOwnPost','update a post by author himself',$bizRule);
$task->addChild('updatePost');

$role=$auth->createRole('reader');
$role->addChild('readPost');

$role=$auth->createRole('author');
$role->addChild('reader');
$role->addChild('createPost');
$role->addChild('updateOwnPost');

$role=$auth->createRole('editor');
$role->addChild('reader');
$role->addChild('updatePost');

$role=$auth->createRole('admin');
$role->addChild('editor');
$role->addChild('author');
$role->addChild('deletePost');

首先我们把它转化为更友好的格式:

uagVlRTW0xR

蓝色的盒子代表’角色’, 黄色的盒子代表’任务’以及橘色盒子最细的颗粒度的’行动’. 把’角色’,’任务’,’行动’组合起来就是授权项目.你应该知道所有的授权项目类型是一样的. 下面可以建立一些’角色’,’任务’的授权项目(它们是相同的东西). 不同类型的授权项目是为了命名上的区别. 你不要局限于这三种层级的授权: 可以有多层级的’角色’, ‘任务’和’行动’. 你也可以跳过一些层级. 惟一的限制是角色的等级比任务高,任务的等级比行动高.

现在我们来快速看一下博客系统的授权等级. 所有的都很符合逻辑. 最弱的角色是’reader': 仅仅允许’read’. author角色有更多的权力: 他也可以创建和更新自己的博客. Editor角色可以阅读文章和更新所有的人文章. 当然, admin角色可以做任何事情.

如果你熟悉面向对象的继续, 之前的知识可能会让你产生疑惑. 在对象树中的子对象继续了父对象的所有特性. 这一结论导致树最底端的对象有更多的特性, 顶端的对象拥有基本特性. 在yii的授权继承中这正好相反. 在整个授权系统中, 授权等级最底层的项目代表基本的操作, 最顶层的授权项目(通常是角色)在拥有更强大的权力.

现在继续的概念是很清晰的, 我们来了解一下是如何做授权检查的. 检查当前用户是否允许执行一个操作,你可以调用checkAccess访问,例如:

if(Yii::app()->user->checkAccess('createPost'))
{
    // create post
}

Yii是怎样通过层次结构来检查授权访问的? 尽管对于文章的剩余内容,理解下面这些是不必须的, 我提供了Yii代码中代码片断来供你参考:

if(($item=$this->getAuthItem($itemName))===null)
 return false;
Yii::trace('Checking permission "'.$item->getName().'"','system.web.auth.CDbAuthManager');
if($this->executeBizRule($item->getBizRule(),$params,$item->getData()))
{
 if(in_array($itemName,$this->defaultRoles))
  return true;
 if(isset($assignments[$itemName]))
 {
  $assignment=$assignments[$itemName];
  if($this->executeBizRule($assignment->getBizRule(),$params,$assignment->getData()))
   return true;
 }
 $sql="SELECT parent FROM {$this->itemChildTable} WHERE child=:name";
 foreach($this->db->createCommand($sql)->bindValue(':name',$itemName)->queryColumn() as $parent)
 {
  if($this->checkAccessRecursive($parent,$userId,$params,$assignments))
   return true;
 }
}
return false;

当你调用checkAccess的时候Yii开始递归沿授权等级向上检查每一个业务规则.例如你可以这样调用:

Yii::app()->user->checkAccess('readPost')

Yii首先检查readPost的业务规则(空的业务规则返回true),然后搜索readPost的父级(在这里是author和editor),检查每一个业务规则. 这个过程当遇到业务规则返回true的时候不会停止;它只要某些规则返回false或到达层次结构的顶部才停止.

然而Yii的checkAccess有几种方式返回true?两种. 首先, 当xxxx. 对于我们的博客系统是reader角色. 默认角色可以在web应用配置文件中设置;

第二种让checkAccess返回true的的方式是建立一个基本定义的授权指定对. 在代码中,我们可以看到这样:

$auth->assign('reader','Pete');
$auth->assign('author','Bob');
$auth->assign('editor','Alice');
$auth->assign('admin','John');

这等同于给用户分配角色.不仅仅是分配角色, 任务和行动也可以分配给用户. 在现实世界将它们存储在数据库中. 你可以使用CDbAuthManager来实现这一过程.

我们继续讨论checkAccess. 层次迭代开始之前, Yii收集用户的所有授权项目, 检查当前的授权项目是否在授权列表中. 如果是则停止迭代,返回一个positive result.

我们实现了一个安全的’update post’用户动作. 登陆到博客系统中的用户需要通过我们的授权检查才可以编辑博文.因此最合适的检查权限的地方应该在控制器动作的最前面.

public function actionUpdatePost()
{
 if(!Yii::app()->user->checkAccess('updatePost'))
  Yii::app()->end();

 // ... more code
}

假如当前用户是Alice.我们来看看yii是怎么处理授权等级的. 由于updateOwnPost是updatePost的直接父级,所以返回false, 然后yii找到另一个父节点的授权项目editor角色,返回true. 最终, Alice允许更新博客. 假如Bob登陆会是怎样呢?In this case the branch of the hierarchy going through the editor item is also processed but no item along it returns true. The only possible way for the access check to succeed is then to go through the updateOwnPost item.

如果updateOwnPost有一个复杂的业务规则要求提供发表人的id,而不是一个总为真值的空业务规则. 我们怎么来提供这样的参数给业务规则呢?在checkAccess的参数形式中. 为了达到这样的目的,需要把控制的方法修改成如下形式:

public function actionUpdatePost()
{
 // here we obtain $post, probably via active record ...

 if(!Yii::app()->user->checkAccess('updatePost', array('post'=>$post)))
  Yii::app()->end();

 // ... more code
}

尽管updateOwnPost对Bob返回true, 验证的迭代器仍然会继续进行. 它会在到达author条目的时候停止并返回成功。

我认为你可以弄清楚当Pete和John登陆的时候Yii是怎么检查它们权限的.

返回上边的代码片断, 我们给业务规则为空的updatePost操作提供了post参数. 这是事实,但不是全部. 事实上Yii会传递相同的参数给每一个继承的迭代器. 如果项目的业务规则为空,则会简单的忽略掉它们. 如果需要,它仅仅会使用必须的参数。

这会导致两种可能的参数传递策略. 第一种是记住所有在迭代中可能会到达的项目,提供精确个数的参数给它们,这样做的优点会使代码更加简洁和高效;第二种是无论它们是否需要, 总是传递所有的参数给每一个验证项目。这会使代码混乱,降低性能.

这仅仅是Yii中RBAC基本的概念, 更先进的模型请参考<<Yii权威指南>>和类参考文档. 还有几个已经实现RBAC管理界面的扩展.

总结:

1. 找到当前用户所有的授权项目
2. 找到要检查项目的‘父项目’。递归检查。如果‘业务规则’返回false则返回false,
3. 如果业务规则返回true,则继续检查当前检查的授权项目是否已经分配给了该用户,如果是则返回true,结束。如果返回false,则继续检查下一个授权项目直到最高层级为止