ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
> 原文出处:https://jellybool.com/post/programming-with-yii2-user-access-controls 上一篇文章讲了[用户的注册,验证和登录](https://jellybool.com/post/programming-with-yii2-integrating-user-registration),这一篇文章按照约定来说说Yii2之中的用户和权限控制。 你可以直接到[Github](https://github.com/JellyBool/helloYii)下载源码,以便可以跟上进度,你也可以重头开始,一步一步按照这个教程来做。 > 鉴于本教材基于Yii2 Basic,所以对RBAC的详细讲解我后面再单独出文章来说说吧,这里主要是简单地说一说权限控制 ![替代文字](https://box.kancloud.cn/2015-08-15_55cf2e8cb1815.png) 上一篇文章所实现的功能还比较简单,可以发一条状态,但是不知道你注意到没有,如果是没有注册的用户也可以使用我们的应用(类似小微博)来发状态,这是不符合情理的。正确的做法是在用户没有注册,登录之前,我们甚至都不应该给没有注册的用户看到我们创建状态的页面,即是`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) ~~~ 数据表的外键设置好之后,我们就可以来声明`Status`和`User`的关系了,不过在开始之前需要修改一下`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保存到`Status`的`created_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()`这个方法。 这样一刷新之后,我们就可以看到创建这条状态的用户`id`和`email`了。 ![替代文字](https://box.kancloud.cn/2015-08-15_55cf2e8d29bc9.png) ## 探寻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_USER`,`ROLE_MODERATOR`,`ROLE_ADMIN`,`USER`可以发表状态,`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(删除按钮)**,你将会看到一个报错的页面: ![替代文字](https://box.kancloud.cn/2015-08-15_55cf2e8d73d06.png) 我们手动创建的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)[https://github.com/JellyBool/helloYii](https://github.com/JellyBool/helloYii) ## 下一节 下一节尝试集成一个编辑器和做一下url的美化,内容应该会比较简单