Yii2系列教程五:簡單的用戶權限管理


上一篇文章講了用戶的注冊,驗證和登錄,這一篇文章按照約定來說說Yii2之中的用戶和權限控制。

你可以直接到Github下載源碼,以便可以跟上進度,你也可以重頭開始,一步一步按照這個教程來做。

鑒於本教材基於Yii2 Basic,所以對RBAC的詳細講解我后面再單獨出文章來說說吧,這里主要是簡單地說一說權限控制

替代文字

上一篇文章所實現的功能還比較簡單,可以發一條狀態,但是不知道你注意到沒有,如果是沒有注冊的用戶也可以使用我們的應用(類似小微博)來發狀態,這是不符合情理的。正確的做法是在用戶沒有注冊,登錄之前,我們甚至都不應該給沒有注冊的用戶看到我們創建狀態的頁面,即是http://localhost:8999/status/create就不應該讓游客看到,更不用說編輯和刪除一條狀態(status)了。

權限控制

什么是權限控制?個人覺得在一個Web應用當中,有以下幾種常見的角色和權限控制:

1. 游客,也就是沒有注冊的用戶,一般這個權限是最小的,對於一些需要登錄訪問的頁面沒有訪問權限 2. 用戶,這里的用戶特指注冊用戶,注冊過后的用戶一般可以使用整個web應用的主要功能,比如我們這里的發表一條狀態(status) 3. 作者,這個不知道確切應該使用什么名詞來描述,作者是在用戶注冊之后的一個權限判斷,比如A發表的status狀態,B君不能進行編輯,刪除等,反之亦然。 4. 管理員,這里的管理員通常會是應用的開發者(所有者,或者應該這么說),幾乎可以說是對站點的所有權限都有 

Yii2自帶的權限控制默認只支持兩個角色:

  1. guest(游客,沒有登錄的,用?表示)

  2. authenticated (登錄了的,用@表示)

在這里我們需要實現的是對這兩種不同的角色指定不同的訪問權限,就是為他們分配不同的可以訪問的控制器或者方法。

目前我們如果直接點擊導航欄的Status,我們還是可以在沒有登錄的情況之下進行發表狀態(status),所以我們需要改一下我們的代碼和邏輯,Yii2在這方面的控制做得非常好,其實實現這個我們只需要修改一下StatusController.php里面的behaviors()方法而已,在這里面加入一段access設置:

public function behaviors() { return [ 'verbs' => [ 'class' => VerbFilter::className(), 'actions' => [ 'delete' => ['post'], ], ], 'access' => [ 'class' => AccessControl::className(), 'only' => ['index','create','update','view'], 'rules' => [ // allow authenticated users [ 'allow' => true, 'roles' => ['@'], ], // everything else is denied ], ], ]; } 

加上access這一段之后,我們再次點擊Status,Yii2就會將未登錄的我重定向到登錄頁面。

而且,這個時候,一旦你登入進去,Yii會默認自動跳轉到上一個url,也就是我們剛剛點擊的status/index

添加映射關系

用戶一旦登錄進來之后,我們就可以通過下面這行代碼來獲取用戶的id了:

Yii::$app->user->getId(); 

一旦用戶的id獲取到,我們可以做的事就很多了。這里我們先來將一條狀態和用戶聯系起來,也就是添加用戶與說說的映射關系。要實現這個目標我們需要先修改我們的數據表(體驗一下當初設計數據表考慮不周全的情況):

./yii migrate/create extend_status_table_for_created_by
Yii Migration Tool (based on Yii v2.0.6) Create new migration '/Users/jellybool/Desktop/helloYii/migrations/m150806_034325_extend_status_table_for_created_by.php'? (yes|no) [no]:yes New migration created successfully. 

打開對應的migration文件,編輯up()down()方法,如果你想加入數據庫的事務管理功能,你可以使用safeUp()safeDown()方法

public function up() { $this->addColumn('{{%status}}','created_by',Schema::TYPE_INTEGER.' NOT NULL'); $this->addForeignKey('fk_status_created_by', '{{%status}}', 'created_by', '{{%user}}', 'id', 'CASCADE', 'CASCADE'); } public function down() { $this->dropForeignKey('fk_status_created_by','{{%status}}'); $this->dropColumn('{{%status}}','created_by'); } 

我們需要為status表添加一個created_by字段,並且將它跟user表的id設為外鍵關系。

如果你在status表里面有一條數據記錄,你需要先刪除這一條記錄,不然可能會報錯。

執行migrate/up:

./yii migrate/up
Yii Migration Tool (based on Yii v2.0.6) Total 1 new migration to be applied: m150806_034325_extend_status_table_for_created_by Apply the above migration? (yes|no) [no]:yes *** applying m150806_034325_extend_status_table_for_created_by > add column created_by integer NOT NULL to table {{%status}} ... done (time: 0.032s) > add foreign key fk_status_created_by: {{%status}} (created_by) references {{%user}} (id) ... done (time: 0.014s) *** applied m150806_034325_extend_status_table_for_created_by (time: 0.059s) 

數據表的外鍵設置好之后,我們就可以來聲明StatusUser的關系了,不過在開始之前需要修改一下User.php里面的內容:

<?php namespace app\models; use dektrium\user\models\User as BaseUser; class User extends BaseUser { public function register() { } } 

直接將原來的User模型的代碼都刪掉,只需要我們上面的代碼就可以了,因為我們使用了Yii2-User, 這里就是使用dektrium\user\models\User這個模型,然后修改一下我們的config/web.php,再我們之前的user中加入幾行代碼:

 'modules' => [ 'user' => [ 'class' => 'dektrium\user\Module', 'confirmWithin' => 21600, // add the following 3 lines 'modelMap' => [ 'User' => 'app\models\User', ], 'cost' => 12, 'admins' => ['admin'] ], ], 

這樣之后,我們的User和Status的對應關系就會建立起來。

然后我們在Status.php寫上以下的說明:

public function getUser() { return $this->hasOne(User::className(), ['id' => 'created_by']); } 

這里聲明的映射關系為hasOne,也就是說,一條狀態status(說說)對應一個用戶(User),我們通過['id' => 'created_by']來指定外鍵映射。

有了Status和User的對應關系之后,我們需要在用戶發表狀態的時候將用戶的id保存到Statuscreated_by這一個字段中,所以我們需要在StatusController中的actionCreate方法中加上一行代碼:

if ($model->load(Yii::$app->request->post())) { $model->created_by = Yii::$app->user->getId();//add this line $model->created_at = time(); $model->updated_at = time(); if ($model->save()) { return $this->redirect(['view', 'id' => $model->id]); } } 

這里需要確認的是,你需要保證create方法只能是登錄進來的用戶才能訪問觸發。

為了更好地展示一條狀態stutas的信息,我們修改一下展示狀態的視圖文件:status/view.php :

<?= DetailView::widget([ 'model' => $model, 'attributes' => [ 'id', 'user.email', // add this line 'message:ntext', 'created_by', // add this line 'permissions', 'created_at', 'updated_at', ], ]) ?> 

上面的user.email中的user其實是觸發Status::getUser()這個方法。

這樣一刷新之后,我們就可以看到創建這條狀態的用戶idemail了。

替代文字

探尋RBAC

上面的一些列設置和代碼更改,已經實現了一小部分的用戶控制:登錄的用戶才能發表status。然而這還不能滿足我們在日常使用的需求,比如我們現在怎么確定一個用戶能不能對某條狀態進行修改和刪除?或者說,管理員的角色在哪里體現呢?現在貌似都是平等的角色,相同的權限,對於登錄的用戶來說。

鑒於官方文檔或者很多關於Yii2 RBAC的資料都是基於Yii2 Advanced Template,而我們一開始使用的是Yii2 Basic Template,並且我們也引入Yii2-User,所以這里我們嘗試來自己實現一點點的用戶權限控制。

首先我們需要在User中定義一些跟角色(role)相關的規定,比如根據不同的用戶角色來賦予不同的常量:

class User extends BaseUser { const ROLE_USER = 10; const ROLE_MODERATOR = 20; const ROLE_ADMIN = 30; } 

上面的代碼寫在User模型里面,這里定義了三種角色,ROLE_USERROLE_MODERATORROLE_ADMINUSER可以發表狀態,MODERATOR可以修改但是不可以刪除,ADMIN可以修改和刪除。

然后在helloYii/目錄之下創建一個components/目錄,里面新建一個AccessRule.php文件:

<?php namespace app\components; use app\models\User; class AccessRule extends \yii\filters\AccessRule { /** * @inheritdoc */ protected function matchRole($user) { if (count($this->roles) === 0) { return true; } foreach ($this->roles as $role) { if ($role === '?') { if ($user->getIsGuest()) { return true; } } elseif ($role === User::ROLE_USER) { if (!$user->getIsGuest()) { return true; } // Check if the user is logged in, and the roles match } elseif (!$user->getIsGuest() && $role === $user->identity->role) { return true; } } return false; } } 

這里就直接借用Yii2自帶的\yii\filters\AccessRule來控制權限規則。但是由於Yii2-User在創建user數據表的時候並沒有role這個字段,所以我們需要手動添加,你可以直接在mysql敲命令行,或者也可以通過數據庫管理工具來添加。

最后更新一下我們的StatusController.php文件,這里的behaviors()方法會做出一些調整:

<?php namespace app\controllers; use Yii; use app\models\Status; use app\models\StatusSearch; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; use yii\filters\AccessControl; use app\components\AccessRule; use app\models\User; /** * StatusController implements the CRUD actions for Status model. */ class StatusController extends Controller { public function behaviors() { return [ 'verbs' => [ 'class' => VerbFilter::className(), 'actions' => [ 'delete' => ['post'], ], ], 'access' => [ 'class' => AccessControl::className(), // We will override the default rule config with the new AccessRule class 'ruleConfig' => [ 'class' => AccessRule::className(), ], 'only' => ['index','create', 'update', 'delete'], 'rules' => [ [ 'actions' => ['index','create'], 'allow' => true, // Allow users, moderators and admins to create 'roles' => [ User::ROLE_USER, User::ROLE_MODERATOR, User::ROLE_ADMIN ], ], [ 'actions' => ['update'], 'allow' => true, // Allow moderators and admins to update 'roles' => [ User::ROLE_MODERATOR, User::ROLE_ADMIN ], ], [ 'actions' => ['delete'], 'allow' => true, // Allow admins to delete 'roles' => [ User::ROLE_ADMIN ], ], ], ], ]; } 

我們上面根據不同等級的用戶賦予不同的訪問權限,這時候,如果你先logout出來,再登錄回去,你還是可以看到這些status,但是一旦你點擊delete(刪除按鈕),你將會看到一個報錯的頁面:

替代文字

我們手動創建的role是成功,但是我們怎么給一個注冊的用戶默認的權限呢,我們這里就是想實現在新用戶注冊的時候賦予用戶ROLE_USER的角色和權限。由於Yii2-User是在vendor\dektrium\yii2-user\models\RegistrationForm.php這個文件里面進行創建新的用戶的,我門這里只要修改一個小地方,找到register()方法:

  public function register() { if ($this->validate()) { $user = $this->module->manager->createUser([ 'email' => $this->email, 'username' => $this->username, 'password' => $this->password, 'role'=>10, // add this line User::ROLE_USER; ]); return $user->register(); } return false; } 

添加'role'=>10就可以了。

如果你想證明一下我們的權限是否正確,你可以手動修改數據庫中的role字段的數值,然后在進行修改和刪除等操作,看看是否可以正確運行。

權限控制其實可以說是Yii2的一大特色和亮點,在這里可能並沒有說得很清晰,只是簡單地實現了一些規則,有機會借助Yii2 Advanced Template來實現一下。

源碼會放在 Github:https://github.com/JellyBool/helloYii


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM