在任何系统中,权限设计是最基础的东西,在 Yii2 中,使用授权的形式来检验一个用户有足够权限来做些事情。
Yii提供两种授权方法:访问控制过滤(ACF)和基于角色的访问控制(RBAC)。
一、Access Control Filter 访问控制过滤
访问控制过滤(ACF)是一个简单的授权方法,非常适合于在只需要一些简单访问控制的程序中使用。正如名字所标示的,访问控制过滤(ACF)是一个动作过滤,它可以作为一个扩展附加到一个控制器或是一个模块。
public function behaviors()
{
return [
'access' => [
'class' => yii\filters\AccessControl::className(),
//'only' => ['logout', 'signup'], //只对 logout signup 进行验证
'rules' => [ //[[yii\filters\AccessRule|accessrules]]
//所有访客都可以访问signup方法
[
'actions' => ['signup'],
'allow' => true,
'roles' => ['?'],// matches a guest user (not authenticated yet)
],
//已授权的用户可以访问logout
[
'actions' => ['logout'],
'allow' => true,
'roles' => ['@'], //matches an authenticated user
],
//匿名函数验证
[
'actions' => ['special-callback'],
'allow' => true,
'matchCallback' => function ($rule, $action) {
return date('d-m') === '31-10';
}
],
//others deny
],
],
];
}
当访问控制过滤(ACF)进行授权检查时,它会从上到下地一个接着一个地测试那些规则直至找到一个相符的。相符的允许值将立即用于判断用户是否被授权。如果没有一个规则符合,这就意味着这个用户“没有”被授权,访问控制过滤(ACF)将阻止它进一步的动作请求。
默认情况下,当访问控制过滤(ACF)探测到一个用户没有被授权进入当前动作时,它只会作下面的工作:
如果这个用户是个访客,ACF将会调用[[yii\web\User::loginRequired()]],将浏览器重定向到配置的登录页面。
如果用户已有授权,ACF会抛出一个[[yii\web\ForbiddenHttpException]]。
你可以通过定义配置[[yii\filters\AccessControl::denyCallback]]的属性来对这个扩展进行自定义:
[
'class' => AccessControl::className(),
'denyCallback' => function ($rule, $action) {
throw new \Exception('You are not allowed to access this page 您没有被允许访问这个页面!');
}
]
[[yii\filters\AccessRule|Access rules]] 验证规则是很灵活的,支持许多选项,还可以有IP、请求类型、匿名函数等验证,具体的可以跟踪到 [[yii\filters\AccessRule]] 查看。
[[yii\filters\AccessRule]]:指定是一个“允许”规则还是一个“禁止”规则。
[[yii\filters\AccessRule::actions|actions]]:指定这个规则与哪些动作匹配。这应该是一个动作的ID的数组。比较是区分大小写的。如果这个选项是空的或是没有设置,这意味着这个规则适用于所有的动作。
[[yii\filters\AccessRule::controllers|controllers]]:指定这个规则与哪些控制器匹配。这应该是一个控制器的ID的数组。比较是区分大小写的。如果这个选项是空的或是没有设置,这意味着这个规则适用于所有的控制器。
[[yii\filters\AccessRule::roles|roles]]:指定这个规则与哪些用户角色匹配。两个特别的用户角色已经被确立,它们将通过[[yii\web\User::isGuest]]来检查。
?:匹配访客(还没有被授权)
@:匹配一个已授权用户
使用其他角色名称的已授权用户,将会调用[[yii\web\User::can()]],就走到基于角色的访问控制(RBAC)了。如果这个选项是空的或是没有设置,这意味着这个规则适用于所有的角色。
[[yii\filters\AccessRule::ips|ips]]:指定这个规则匹配哪一个[[yii\web\Request::userIP|clientIP addresses]]。一个IP地址可以在末尾包含一个通配符,这样就可以匹配具有相同前缀的所有IP地址。比如,“192.168.”,匹配“192.168.”节中所有的IP地址。如果这个选项是空的或是没有设置,这意味着这个规则适用于所有的IP。
[[yii\filters\AccessRule::verbs|verbs]]:指定这个规则匹配哪种请求方法(比如GET,POST)。比较是区分大小写的。
[[yii\filters\AccessRule::matchCallback|matchCallback]]:指定一个PHP可调用,它将被调用来检查这个规则是否可以执行。
[[yii\filters\AccessRule::denyCallback|denyCallback]]:指定一个PHP可调用,在这个规则拒绝访问时它将被调用。
二、基于角色的访问控制(RBAC)
基于角色的访问控制(RBAC)是用非常灵活的方式来控制访问,提供了简单而又功能强大的集中的访问控制。
Yii提供了两种鉴权管理器:yii\rbac\PhpManager 和 yii\rbac\DbManager。
前者使用一个PHP脚本文件管理鉴权数据,而后者是把数据存储在数据库里面。PhpManager主要适用于授权的数据不是太大,不需要经常变动的角色和权限管理的应用(例如,一个个人博客系统的授权数据)。DbManager 更适用于较复杂的授权数据。
2.1 基于文件的配置的RBAC
在定义鉴权数据并执行访问检查之前,必须先配置authManager组件。
'authManager' => [
'class' => 'yii\rbac\PhpManager',
'defaultRoles' => ['guest'],
],
默认的,yii\rbac\PhpManager 在三个文件里面存储RBAC数据:
@app/rbac/items.php //定义角色和许可,在角色和许可间建立关系
@app/rbac/assignments.php’ //分配角色给用户
@app/rbac/rules.php’ //定义规则,把规则跟角色和许可关联起来 规则就是访问检查的时候执行的一段代码,它来决定相应的角色或者权限是否适用于当前用户。
请确保这三个文件对服务器进程可写,有时你需要手动的去创建这些文件。
items.php
return [
'createPost' => [
'type' => 2,
'description' => 'Create a post',
],
'updatePost' => [
'type' => 2,
'description' => 'Update post',
],
'read' => [
'type' => 2,
'description' => 'Read',
],
'author' => [
'type' => 1,
'children' => [
'createPost',
'read',
'updateOwnPost',
],
],
'admin' => [
'type' => 1,
'children' => [
'updatePost',
'author',
],
],
'updateOwnPost' => [
'type' => 2,
'description' => 'Update own post',
'ruleName' => 'isAuthor',
'children' => [
'updatePost',
],
],
];
assignments.php
return [
2 => [
'author',
],
1 => [
'admin',
],
];
rules.php
return [
'isAuthor' => 'O:19:"yii\\rbac\\AuthorRule":3:{s:4:"name";s:8:"isAuthor";s:9:"createdAt";N;s:9:"updatedAt";N;}',
];
如果刚开始不太清楚结构,可以通过authManager提供的API创建一个控制台命令来生成示例代码。
namespace console\controllers;
use yii\console\Controller;
class RbacController extends \yii\console\Controller
{
public function actionInit()
{
$auth = \Yii::$app->authManager;
// 添加”创建文章”许可
$createPost = $auth->createPermission('createPost');
$createPost->description = 'Create a post';
$auth->add($createPost);
// 添加”更新文章”许可
$updatePost = $auth->createPermission('updatePost');
$updatePost->description = 'Update post';
$auth->add($updatePost);
// 添加”查看文章”许可
$reader = $auth->createPermission('read');
$reader->description = 'Read';
$auth->add($reader);
// 添加“作者”角色,接着赋予这个角色“创建文章”的许可
$author = $auth->createRole('author');
$auth->add($author);
$auth->addChild($author, $createPost);
$auth->addChild($author, $reader);
// 添加“管理员”角色,接着赋予这个角色“更新文章”的许可
// 与“作者角色的一样
$admin = $auth->createRole('admin');
$auth->add($admin);
$auth->addChild($admin, $updatePost);
$auth->addChild($admin, $author);
//添加一条规则
$rule = new \yii\rbac\AuthorRule;
$auth->add($rule);
//添加“updateOwnPost”权限,并且和上面的规则关联起来
$updateOwnPost = $auth->createPermission('updateOwnPost');
$updateOwnPost->description = 'Update own post';
$updateOwnPost->ruleName = $rule->name;
$auth->add($updateOwnPost);
// "updateOwnPost" will be used from "updatePost"
$auth->addChild($updateOwnPost, $updatePost);
// allow "author" to update their own posts
$auth->addChild($author, $updateOwnPost);
// 分配角色给用户。1和2是用户ID,用IdentityInterface::getId()获取到的
// 经常出现在您的User模块中。
$auth->assign($author, 2);
$auth->assign($admin, 1);
}
}
当鉴权数据准备好之后,就可以进行访问检查了。可以通过配置ACF进行自动校验,也可以在某一个要操作的方法中进行校验。前者其实也是调用的后者的方法。
if (\Yii::$app->user->can('createPost')) {
// create post
}
2.2 以DB为基础的存储RBAC
如果是数据经常变动并且后台需要灵活管理,用DB形式的是比较好的一个选择。
Yii2 的RBAC一共是用到了五张表:四张auth表和一张user表。其中 auth 表位于 vendor/yiisoft/yii2/rbac/migration, 可使用命令生成 yii migrate –migrationPath=@yii/rbac/migrations/
auth_item :存储角色或权限的表。name为权限名,用 type 字段来标识是角色还是权限, 1 为角色 , 2 为权限 。
auth_item_child:角色权限关联表。角色是一组权限的集合,和上面的auth_item相关联,用来保存角色和权限的关系,两个字段均对应 auth_item 表的 name 字段。
角色可以包含角色或权限,权限可以包含权限。
如果要得到一个角色的所有的权限,要做两方面的查找,一个是递归查找当前权限所有的子权限, 一个是查看所包含的角色的所有的权限以及子权限。所以在使用中不建议让权限继承,只让角色继承。而且继承深度也不宜太深。
auth_assignment:权限(角色)和用户的关联表,这个表用来存储给用户分配的角色或者权限,一个用户的权限包含两部分,一部分是所指定的角色代表的权限,一部分就是直接所指定的权限。
auth_rule:规则表 一个用户要执行一个操作除了要看他有没有这个权限外,还要看他的这个权限能不能执行。 auth_item 中还有一个字段: [rule_name] 。这个字段用来标明这个角色或者权限能不能成功执行。
1、配置 Rbac,生成数据表。
'authManager' => [
'class' => 'yii\rbac\DbManager',
'itemTable' => 'auth_item',
'assignmentTable' => 'auth_assignment',
'itemChildTable' => 'auth_item_child',
],
yii migrate --migrationPath=@yii/rbac/migrations/
2、创建一个 许可 Permiassion
public function createPermission($item)
{
$auth = Yii::$app->authManager;
$createPost = $auth->createPermission($item);
$createPost->description = '创建了 ' . $item . ' 许可';
$auth->add($createPost);
}
3、创建一个 角色 roles
public function createRole($item)
{
$auth = Yii::$app->authManager;
$role = $auth->createRole($item);
$role->description = '创建了 ' . $item . ' 角色';
$auth->add($role);
}
4、给角色分配许可
static public function createEmpowerment($items)
{
$auth = Yii::$app->authManager;
$parent = $auth->createRole($items['name']);
$child = $auth->createPermission($items['description']);
$auth->addChild($parent, $child);
}
5、给角色分配用户
static public function assign($item)
{
$auth = Yii::$app->authManager;
$reader = $auth->createRole($item['name']);
$auth->assign($reader, $item['description']);
}
6、验证用户是否有权限
public function beforeAction($action)
{
$action = Yii::$app->controller->action->id;
if(\Yii::$app->user->can($action)){
return true;
}else{
throw new \yii\web\UnauthorizedHttpException('对不起,您现在还没获此操作的权限');
}
}
实际操作与 PhpManager 异曲同工,都是创建权限、创建角色、分配权限、绑定用户。
当然,yii只是为我们提供了基础的授权机制,你尽可以在此基础上大动手脚,比如用户表看不上,改user组件,权限表不符合自己的审美,改组件+数据表。。。
附:
yii\rbac: Item 为角色或者权限的基类,其中用字段type来标识
yii\rbac: Role 为代表角色的类
yii\rbac: Permission 为代表权限的类
yii\rbac: Assignment 为代表用户角色或者权限的类
yii\rbac: Rule 为代表角色或权限能否执行的判定规则
参考: