五:簡單的用戶權(quán)限管理

2018-02-24 15:58 更新

原文出處:https://jellybool.com/post/programming-with-yii2-user-access-controls

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

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

鑒于本教材基于Yii2 Basic,所以對RBAC的詳細(xì)講解我后面再單獨(dú)出文章來說說吧,這里主要是簡單地說一說權(quán)限控制

替代文字

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

權(quán)限控制

什么是權(quán)限控制?個(gè)人覺得在一個(gè)Web應(yīng)用當(dāng)中,有以下幾種常見的角色和權(quán)限控制:

1. 游客,也就是沒有注冊的用戶,一般這個(gè)權(quán)限是最小的,對于一些需要登錄訪問的頁面沒有訪問權(quán)限

2. 用戶,這里的用戶特指注冊用戶,注冊過后的用戶一般可以使用整個(gè)web應(yīng)用的主要功能,比如我們這里的發(fā)表一條狀態(tài)(status)

3. 作者,這個(gè)不知道確切應(yīng)該使用什么名詞來描述,作者是在用戶注冊之后的一個(gè)權(quán)限判斷,比如A發(fā)表的status狀態(tài),B君不能進(jìn)行編輯,刪除等,反之亦然。

4. 管理員,這里的管理員通常會是應(yīng)用的開發(fā)者(所有者,或者應(yīng)該這么說),幾乎可以說是對站點(diǎn)的所有權(quán)限都有

Yii2自帶的權(quán)限控制默認(rèn)只支持兩個(gè)角色:

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

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

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

目前我們?nèi)绻苯狱c(diǎn)擊導(dǎo)航欄的Status,我們還是可以在沒有登錄的情況之下進(jìn)行發(fā)表狀態(tài)(status),所以我們需要改一下我們的代碼和邏輯,Yii2在這方面的控制做得非常好,其實(shí)實(shí)現(xiàn)這個(gè)我們只需要修改一下StatusController.php里面的behaviors()方法而已,在這里面加入一段access設(shè)置:

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這一段之后,我們再次點(diǎn)擊Status,Yii2就會將未登錄的我重定向到登錄頁面。

而且,這個(gè)時(shí)候,一旦你登入進(jìn)去,Yii會默認(rèn)自動跳轉(zhuǎn)到上一個(gè)url,也就是我們剛剛點(diǎn)擊的status/index。

添加映射關(guān)系

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

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

一旦用戶的id獲取到,我們可以做的事就很多了。這里我們先來將一條狀態(tài)和用戶聯(lián)系起來,也就是添加用戶與說說的映射關(guān)系。要實(shí)現(xiàn)這個(gè)目標(biāo)我們需要先修改我們的數(shù)據(jù)表(體驗(yàn)一下當(dāng)初設(shè)計(jì)數(shù)據(jù)表考慮不周全的情況):

./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.

打開對應(yīng)的migration文件,編輯up()down()方法,如果你想加入數(shù)據(jù)庫的事務(wù)管理功能,你可以使用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表添加一個(gè)created_by字段,并且將它跟user表的id設(shè)為外鍵關(guān)系。

如果你在status表里面有一條數(shù)據(jù)記錄,你需要先刪除這一條記錄,不然可能會報(bào)錯(cuò)。

執(zhí)行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)

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

<?php
namespace app\models;

use dektrium\user\models\User as BaseUser;

class User extends BaseUser {

    public function register()
    {

    }
}

直接將原來的User模型的代碼都刪掉,只需要我們上面的代碼就可以了,因?yàn)槲覀兪褂昧薡ii2-User, 這里就是使用dektrium\user\models\User這個(gè)模型,然后修改一下我們的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的對應(yīng)關(guān)系就會建立起來。

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

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

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

有了Status和User的對應(yīng)關(guān)系之后,我們需要在用戶發(fā)表狀態(tài)的時(shí)候?qū)⒂脩舻膇d保存到Statuscreated_by這一個(gè)字段中,所以我們需要在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]);
    }
}

這里需要確認(rèn)的是,你需要保證create方法只能是登錄進(jìn)來的用戶才能訪問觸發(fā)。

為了更好地展示一條狀態(tài)stutas的信息,我們修改一下展示狀態(tài)的視圖文件: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其實(shí)是觸發(fā)Status::getUser()這個(gè)方法。

這樣一刷新之后,我們就可以看到創(chuàng)建這條狀態(tài)的用戶idemail了。

替代文字

探尋RBAC

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

鑒于官方文檔或者很多關(guān)于Yii2 RBAC的資料都是基于Yii2 Advanced Template,而我們一開始使用的是Yii2 Basic Template,并且我們也引入Yii2-User,所以這里我們嘗試來自己實(shí)現(xiàn)一點(diǎn)點(diǎn)的用戶權(quán)限控制。

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

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

}

上面的代碼寫在User模型里面,這里定義了三種角色,ROLE_USER,ROLE_MODERATOR,ROLE_ADMINUSER可以發(fā)表狀態(tài),MODERATOR可以修改但是不可以刪除,ADMIN可以修改和刪除。

然后在helloYii/目錄之下創(chuàng)建一個(gè)components/目錄,里面新建一個(gè)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來控制權(quán)限規(guī)則。但是由于Yii2-User在創(chuàng)建user數(shù)據(jù)表的時(shí)候并沒有role這個(gè)字段,所以我們需要手動添加,你可以直接在mysql敲命令行,或者也可以通過數(shù)據(jù)庫管理工具來添加。

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

<?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
                        ],
                    ],
                ],
            ],
        ];
    }

我們上面根據(jù)不同等級的用戶賦予不同的訪問權(quán)限,這時(shí)候,如果你先logout出來,再登錄回去,你還是可以看到這些status,但是一旦你點(diǎn)擊delete(刪除按鈕),你將會看到一個(gè)報(bào)錯(cuò)的頁面:

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號