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/*"的基礎權限訪問。
最後,我們通過剛剛分配了角色的用戶和未分配權限的用戶分別登陸管理平臺,測測通過界面化操作後,訪問控制是不是生效了?
測試發現,結果正如我們所料!
到此,管理平臺的界面化操作我們不僅實現了,還懂得了如何去分配,更重要的是,我們完全明白了這一整套業務邏輯的實現!