Yii2系列教程五:简单的用户权限管理

JellyBool

JellyBool

上一篇文章讲了[用户的注册,验证和登录] (https://jellybool.com/post/programming-with-yii2-integrating-user-registration),这一篇文章按照约定来说说 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

下一节

下一节尝试集成一个编辑器和做一下url的美化,内容应该会比较简单

Happy Hacking

本文由 JellyBool 创作, 转载和引用遵循 署名-非商业性使用 2.5 中国大陆 进行许可。

共有 4 条评论

Anonymous
修改的评论也不能少于六个字哦!
Anonymous
修改的评论也不能少于六个字哦!
Anonymous
修改的评论也不能少于六个字哦!
Anonymous
修改的评论也不能少于六个字哦!