Yii 2.0 自带rbac数据表分析
一、开发之前有必要先来分析一下数据表
- 首先主体,也就是用户,需要有一张用户表,很简单,我们已经有了,就是数据表 user_backend
- 我们需要有一张角色表和权限表,分别存放角色和权限的数据表
- 另外我们还需要一张主体跟角色的关联表,也就是需要给用户分配角色的存储表
- 最后我们再需要一张角色跟权限的关联表
- 官方自带的rbac在哪呢?在 vendor\yiisoft\yii2\rbac 目录下
二、我们用migrate生成官方自带的数据表
部分同学直接拷贝sql执行,但是sql本身未设定utf8模式,导致后面乱码,此处我们统一使用migrate进行操作
- CLI模式即命令行模式下,在vendor同级目录执行以下命令
# linux系统运行
./yii migrate --migrationPath=@yii/rbac/migrations/
# window系统运行
yii migrate --migrationPath=@yii/rbac/migrations/
- 回车后输入yes…然后报错了,我们看看具体的报错信息
Exception 'yii\base\InvalidConfigException' with message 'You should configure "authManager" component to use database before executing this migration.'
提示我们说在执行这条migration之前让我们先配置下authManager组件!
3. 配置下authManager组件
打开 backend\config\main.php 文件,在 components 数组中加入 authManager 组件。
//authManager有PhpManager和DbManager两种方式,
//PhpManager将权限关系保存在文件里,这里使用的是DbManager方式,将权限关系保存在数据库.
"authManager" => [
"class" => 'yii\rbac\DbManager',
'defaultRoles' => ['guest'],
],
然后我们再次执行上面的migrate命令,控制台提示我们成功创建了4张数据表
三、RBAC初体验
- 创建blog表
CREATE TABLE `blog` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL DEFAULT '' COMMENT '标题',
`content` text NOT NULL COMMENT '内容',
`views` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '点击量',
`is_delete` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除 0未删除 1已删除',
`created_at` datetime NOT NULL COMMENT '添加时间',
`updated_at` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='博客表';
- gii生成模板,不会的请参考
这篇文章
此时的博客一系列操作都是游离在RBAC和ACF之外的,也就是任何人都可以访问的
利用 auth_assignment、auth_item 和 auth_item_child 表来实现rbac的授权
- 手动创建RbacController 控制器做练习
<?php
/**
* Created by: Joker
* Date: 2020/1/3
* Time: 12:22
*/
namespace backend\controllers;
use Yii;
use yii\web\Controller;
class RbacController extends Controller
{
public function actionInit ()
{
// 这个是我们上节课添加的authManager组件,组件的调用方式没忘吧?
$auth = Yii::$app->authManager;
// 添加 "/blog/index" 权限
$blogIndex = $auth->createPermission('/blog/index');
$blogIndex->description = '博客列表';
$auth->add($blogIndex);
// 创建一个角色 '博客管理',并为该角色分配"/blog/index"权限
$blogManage = $auth->createRole('博客管理');
$auth->add($blogManage);
$auth->addChild($blogManage, $blogIndex);
// 为用户 test1(该用户的uid=1) 分配角色 "博客管理" 权限
$auth->assign($blogManage, 1); // 1是test1用户的uid
}
}
访问 rbac/init 就意味着我们为用户id为1的后台用户分配了角色 “博客管理”,而博客管理包含着"blog/index"的访问权限。
分配好了以后我们还需要为 blog/index 实现一个校验方法,只有分配了权限的用户才可以访问,否则肯定要给用户403提示了!
- BlogController 控制器的 index 方法做如下修改
public function actionIndex()
{
// 权限判断
if (!Yii::$app->user->can('/blog/index')) {
throw new \yii\web\ForbiddenHttpException("没权限访问.");
}
$searchModel = new BlogSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
退出登录或者使用其他用户登录的情况下访问 blog/index 会发现,真的提示403
- 初步优化
打开BlogController 增加一个 beforeAction,代码如下:
public function beforeAction($action)
{
$currentRequestRoute = $action->getUniqueId();
if (!Yii::$app->user->can('/' . $currentRequestRoute)) {
throw new \yii\web\ForbiddenHttpException("没权限访问.");
}
return true;
}
然后把index action的 Yii::$app->user->can 那段判断隐藏掉,现在试图访问下 index 操作,看看是不是达到了同样的效果啦
- 高级优化
backend\components 目录(没有就创建)下新增一个类文件 MyBehavior.php
<?php
/**
* Created by: Joker
* Date: 2020/1/3
* Time: 12:40
*/
namespace backend\components;
use Yii;
class MyBehavior extends \yii\base\ActionFilter
{
public function beforeAction ($action)
{
var_dump(111);
return true;
}
}
然后删除BlogController 的 beforeAction 修改 BlogController 的 behaviors 方法如下
public function behaviors()
{
return [
//附加行为
'myBehavior' => \backend\components\MyBehavior::className(),
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'delete' => ['POST'],
],
],
];
}
此时我们访问下 /blog 会发现,页面的最上面有打印111出来。
- 终极优化
在项目的配置文件 main.php中,components 同级添加如下代码
'as myBehavior2' => backend\components\MyBehavior::className(),
四、RBAC的分配
- 先把 博客的 index、view、create、update 和 delete 这5个操作的路由权限添加并分配给"博客管理"这个角色,打开 RbacController,添加如下代码
// 添加权限
public function actionInit2 ()
{
$auth = Yii::$app->authManager;
// 添加权限, 注意斜杠不要反了
$blogView = $auth->createPermission('/blog/view');
$auth->add($blogView);
$blogCreate = $auth->createPermission('/blog/create');
$auth->add($blogCreate);
$blogUpdate = $auth->createPermission('/blog/update');
$auth->add($blogUpdate);
$blogDelete = $auth->createPermission('/blog/delete');
$auth->add($blogDelete);
// 分配给我们已经添加过的"博客管理"权限
$blogManage = $auth->getRole('博客管理');
$auth->addChild($blogManage, $blogView);
$auth->addChild($blogManage, $blogCreate);
$auth->addChild($blogManage, $blogUpdate);
$auth->addChild($blogManage, $blogDelete);
}
访问 rbac/init2 添加权限
之后要实现的验证授权
- 在 backend\components 中创建行为类 AccessControl,
<?php
namespace backend\components;
use Yii;
use yii\web\ForbiddenHttpException;
class AccessControl extends \yii\base\ActionFilter
{
/**
* 对用户请求的路由进行验证
* @return true 表示有权访问
*/
public function beforeAction ($action)
{
// 当前路由
$actionId = $action->getUniqueId();
$actionId = '/' . $actionId;
// 当前登录用户的id
$user = Yii::$app->getUser();
$userId = $user->id;
// 获取当前用户已经分配过的路由权限
// 写的比较简单,有过基础的可自行完善,比如解决"*"的问题,看不懂的该行注释自行忽略
$routes = [];
$manager = Yii::$app->getAuthManager();
foreach ($manager->getPermissionsByUser($userId) as $name => $value) {
if ($name[0] === '/') {
$routes[] = $name;
}
}
// 判断当前用户是否有权限访问正在请求的路由
if (in_array($actionId, $routes)) {
return true;
}
$this->denyAccess($user);
}
/**
* 拒绝用户访问
* 访客,跳转去登录;已登录,抛出403
* @param $user 当前用户
* @throws ForbiddenHttpException 如果用户已经登录,抛出403.
*/
protected function denyAccess($user)
{
if ($user->getIsGuest()) {
$user->loginRequired();
} else {
throw new ForbiddenHttpException('不允许访问.');
}
}
}
- 修改 BlogController 的 behaviors 方法,配置一下我们的行为类
public function behaviors()
{
return [
//附加行为
// 'myBehavior' => \backend\components\MyBehavior::className(),
'as access' => [
'class' => 'backend\components\AccessControl',
],
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'delete' => ['POST'],
],
],
];
}
注:这里写的 ‘as access’ 只是一个名字而已,没什么其他的含义。
五、权限管理的界面化
yii2-admin组件配置及使用
composer require mdmsoft/yii2-admin "~2.0"
安装好了之后,我们打开 backend\config\main.php,配置如下
return [
//......
'modules' => [
'admin' => [
'class' => 'mdm\admin\Module',
],
//......
],
'aliases' => [
'@mdm/admin' => '@vendor/mdmsoft/yii2-admin',
],
'components' => [
//......
'authManager' => [
'class' => 'yii\rbac\DbManager',
'defaultRoles' => ['guest'],
],
//......
],
'as access' => [
'class' => 'mdm\admin\components\AccessControl',
'allowActions' => [
//这里是允许访问的action,不受权限控制
//controller/action
"*"
]
],
//......
];
yii2-admin 是一套封装好的界面化权限管理组件,按理说我们上面配置好了,访问如下路由:
admin
admin/route
admin/permission
admin/role
admin/assignment
1、首先我们需要把路由添加到数据表中,这些路由就是我们以后要分配操作的。
访问 /admin/route ,把 admin/* 移动到右侧。这里面有几个注意点我们讲一下:
1):左侧是未分配的路由,那这些路由是怎么获取到呢?这就利用到php反射的基础知识了,不知道的可以回去补补课啦~
2):右侧是已经分配过的路由,是添加到数据表 auth_item 中的路由
3):页面上的 Add 按钮,这个有什么作用?不是说左侧会自动获取类以及对应的方法吗?注意啦,左侧反射显示出的路由呢,是有缓存的,也就是当你刷新某些路由仍然显示不出来的时候,可以自己手动点击 Add 按钮进行添加。路由的格式需要跟左侧解析出来的一致。
2、按照我们上一章节所述,下面这一步理应直接添加角色,然后把路由权限分配给角色。但是,更强大的组件意味着更麻烦、更复杂一点。我想了一上午不知道怎么描述的更通俗易懂,我们画两张图对比理解下:
按照右图的理解,我们应该在把路由权限分配给角色之前,先打开 /admin/permission 创建一个权限集合,然后给其分配路由权限,换言之,这个权限集合就是一个小角色,小角色又是个什么东东? 这里我们只是把 route 分配给 permission 的代称!那按照你的意思还有大角色咯?没错,大角色我们姑且定义为可以容纳路由、权限集合、小角色的简称!注意哦,“小角色”“大角色”只是为了方便大家理解,我们自己这么定义的!
正如我们在了解一下基于角色的访问控制所说,rbac是以角色为基础的访问控制,所以上面所述并不难理解。
我们在第一步中,添加了路由 /admin/,现在我们试着访问 /admin/permission 添加一个权限集合“权限管理”,并把路由 /admin/ 分配给“权限管理”。
友情提醒,添加完权限集合跳转到权限列表页,点击查看详情,选中左侧路由“/admin/*”,移动到右侧即可。
接着我们就应该访问 /admin/role 添加真正意义上的“大角色”了。我们创建一个“管理员”的角色,并为其分配小角色“权限管理”和大角色“博客管理”。
友情提醒:添加完“管理员”回到角色列表后,点击查看详情,选中左侧的角色“博客管理”和权限集合“权限管理”,移动到右侧即可。注意哦,“博客管理”这个角色是我们上节文章中手动创建的。
3、这最后一步自然就是把我们创建好的角色分配给用户啦,注意哦,我们在第二步中引入了“大小角色”,即角色和权限集合,再加上我们对rbac概念的理解,以角色为基础。下面我们来试试界面化操作,把“大小角色”分配给用户吧。
友情提醒,访问/admin/assignment,显示的是用户列表,选择某个用户,点击查看详情,选中左侧的角色“博客管理”“管理员”,移动到右侧即完成了对该用户分配角色的动作。
4、关于授权认证。上一章节中,我们通过行为,手动实现了授权认证。该章节的开始,我们对yii2-admin进行了配置,未对as access的allowActions添加“”之前也是403,这说明,as access的类 mdm\admin\components\AccessControl 内部已经帮我们实现过了权限认证!我们现在通过配置,把allowActions 的“”去掉,新增"site/*"的基础权限访问。
最后,我们通过刚刚分配了角色的用户和未分配权限的用户分别登陆管理平台,测测通过界面化操作后,访问控制是不是生效了?
测试发现,结果正如我们所料!
到此,管理平台的界面化操作我们不仅实现了,还懂得了如何去分配,更重要的是,我们完全明白了这一整套业务逻辑的实现!