授權(quán)(Authorization)

2018-02-24 15:40 更新

授權(quán)

授權(quán)是指驗證用戶是否允許做某件事的過程。Yii提供兩種授權(quán)方法: 存取控制過濾器(ACF)和基于角色的存取控制(RBAC)。

存取控制過濾器

存取控制過濾器(ACF)是一種通過 yii\filters\AccessControl 類來實現(xiàn)的簡單授權(quán)方法, 非常適用于僅需要簡單的存取控制的應(yīng)用。正如其名稱所指,ACF 是一個種行動(action)過濾器?filter,可在控制器或者模塊中使用。當(dāng)一個用戶請求一個 action 時, ACF會檢查 yii\filters\AccessControl::rules 列表,判斷該用戶是否允許執(zhí) 行所請求的action。(譯者注:?action?在本文中視情況翻譯為行動、操作方法等)

下述代碼展示如何在?site?控制器中使用 ACF:

use yii\web\Controller;
use yii\filters\AccessControl;

class SiteController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'only' => ['login', 'logout', 'signup'],
                'rules' => [
                    [
                        'allow' => true,
                        'actions' => ['login', 'signup'],
                        'roles' => ['?'],
                    ],
                    [
                        'allow' => true,
                        'actions' => ['logout'],
                        'roles' => ['@'],
                    ],
                ],
            ],
        ];
    }
    // ...
}

上面的代碼中 ACF 以行為 (behavior) 的形式附加到?site?控制器。 這就是很典型的使用行動過濾器的方法。?only?選項指明 ACF 應(yīng)當(dāng)只 對?login,?logout?和?signup?方法起作用。所有其它的?site?控制器中的方法不受存取控制的限制。?rules?選項列出了 yii\filters\AccessRule,解讀如下:

  • 允許所有訪客(還未經(jīng)認(rèn)證的用戶)執(zhí)行?login?和?signup?操作。?roles?選項包含的問號???是一個特殊的標(biāo)識,代表”訪客用戶”。
  • 允許已認(rèn)證用戶執(zhí)行?logout?操作。@是另一個特殊標(biāo)識, 代表”已認(rèn)證用戶”。

ACF 自頂向下逐一檢查存取規(guī)則,直到找到一個與當(dāng)前 欲執(zhí)行的操作相符的規(guī)則。 然后該匹配規(guī)則中的?allow?選項的值用于判定該用戶是否獲得授權(quán)。如果沒有找到匹配的規(guī)則, 意味著該用戶沒有獲得授權(quán)。(譯者注:?only?中沒有列出的操作,將無條件獲得授權(quán))

當(dāng) ACF 判定一個用戶沒有獲得執(zhí)行當(dāng)前操作的授權(quán)時,它的默認(rèn)處理是:

  • 如果該用戶是訪客,將調(diào)用 yii\web\User::loginRequired() 將用戶的瀏覽器重定向到登錄頁面。
  • 如果該用戶是已認(rèn)證用戶,將拋出一個 yii\web\ForbiddenHttpException 異常。

你可以通過配置 yii\filters\AccessControl::denyCallback 屬性定制該行為:

[
    'class' => AccessControl::className(),
    ...
    'denyCallback' => function ($rule, $action) {
        throw new \Exception('You are not allowed to access this page');
    }
]

yii\filters\AccessRule 支持很多的選項。下列是所支持選項的總覽。 你可以派生 yii\filters\AccessRule 來創(chuàng)建自定義的存取規(guī)則類。

  • yii\filters\AccessRule::allow: 指定該規(guī)則是 "允許" 還是 "拒絕" 。(譯者注:true是允許,false是拒絕)

  • yii\filters\AccessRule::actions:指定該規(guī)則用于匹配哪些操作。 它的值應(yīng)該是操作方法的ID數(shù)組。匹配比較是大小寫敏感的。如果該選項為空,或者不使用該選項, 意味著當(dāng)前規(guī)則適用于所有的操作。

  • yii\filters\AccessRule::controllers:指定該規(guī)則用于匹配哪些控制器。 它的值應(yīng)為控制器ID數(shù)組。匹配比較是大小寫敏感的。如果該選項為空,或者不使用該選項, 則意味著當(dāng)前規(guī)則適用于所有的操作。(譯者注:這個選項一般是在控制器的自定義父類中使用才有意義)

  • yii\filters\AccessRule::roles:指定該規(guī)則用于匹配哪些用戶角色。 系統(tǒng)自帶兩個特殊的角色,通過 yii\web\User::isGuest 來判斷:

    • ?: 用于匹配訪客用戶 (未經(jīng)認(rèn)證)
    • @: 用于匹配已認(rèn)證用戶

    使用其他角色名時,將觸發(fā)調(diào)用 yii\web\User::can(),這時要求 RBAC 的支持 (在下一節(jié)中闡述)。 如果該選項為空或者不使用該選項,意味著該規(guī)則適用于所有角色。

  • yii\filters\AccessRule::ips:指定該規(guī)則用于匹配哪些 yii\web\Request::userIP 。 IP 地址可在其末尾包含通配符?*?以匹配一批前綴相同的IP地址。 例如,192.168.*?匹配所有?192.168.?段的IP地址。 如果該選項為空或者不使用該選項,意味著該規(guī)則適用于所有角色。

  • yii\filters\AccessRule::verbs:指定該規(guī)則用于匹配哪種請求方法(例如GETPOST)。 這里的匹配大小寫不敏感。

  • yii\filters\AccessRule::matchCallback:指定一個PHP回調(diào)函數(shù)用于 判定該規(guī)則是否滿足條件。(譯者注:此處的回調(diào)函數(shù)是匿名函數(shù))

  • yii\filters\AccessRule::denyCallback: 指定一個PHP回調(diào)函數(shù), 當(dāng)這個規(guī)則不滿足條件時該函數(shù)會被調(diào)用。(譯者注:此處的回調(diào)函數(shù)是匿名函數(shù))

以下例子展示了如何使用?matchCallback?選項, 可使你設(shè)計任意的訪問權(quán)限檢查邏輯:

use yii\filters\AccessControl;

class SiteController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'only' => ['special-callback'],
                'rules' => [
                    [
                        'actions' => ['special-callback'],
                        'allow' => true,
                        'matchCallback' => function ($rule, $action) {
                            return date('d-m') === '31-10';
                        }
                    ],
                ],
            ],
        ];
    }

    // 匹配的回調(diào)函數(shù)被調(diào)用了!這個頁面只有每年的10月31號能訪問(譯者注:原文在這里說該方法是回調(diào)函數(shù)不確切,讀者不要和 `matchCallback` 的值即匿名的回調(diào)函數(shù)混淆理解)。
    public function actionSpecialCallback()
    {
        return $this->render('happy-halloween');
    }
}

基于角色的存取控制 (RBAC)

基于角色的存取控制 (RBAC) 提供了一個簡單而強大的集中式存取控制機制。 詳細(xì)的關(guān)于 RBAC 和諸多傳統(tǒng)的存取控制方案對比的詳情,請參閱?Wikipedia

Yii 實現(xiàn)了通用的分層的 RBAC,遵循的模型是?NIST RBAC model. 它通過 yii\rbac\ManagerInterface?application component?提供 RBAC 功能。

使用 RBAC 涉及到兩部分工作。第一部分是建立授權(quán)數(shù)據(jù), 而第二部分是使用這些授權(quán)數(shù)據(jù)在需要的地方執(zhí)行檢查。

為方便后面的講述,這里先介紹一些 RBAC 的基本概念。

基本概念

角色是?權(quán)限?的集合 (例如:建貼、改貼)。一個角色 可以指派給一個或者多個用戶。要檢查某用戶是否有一個特定的權(quán)限, 系統(tǒng)會檢查該包含該權(quán)限的角色是否指派給了該用戶。

可以用一個規(guī)則?rule?與一個角色或者權(quán)限關(guān)聯(lián)。一個規(guī)則用一段代碼代表, 規(guī)則的執(zhí)行是在檢查一個用戶是否滿足這個角色或者權(quán)限時進(jìn)行的。例如,"改帖" 的權(quán)限 可以使用一個檢查該用戶是否是帖子的創(chuàng)建者的規(guī)則。權(quán)限檢查中,如果該用戶 不是帖子創(chuàng)建者,那么他(她)將被認(rèn)為不具有 "改帖"的權(quán)限。

角色和權(quán)限都可以按層次組織。特定情況下,一個角色可能由其他角色或權(quán)限構(gòu)成, 而權(quán)限又由其他的權(quán)限構(gòu)成。Yii 實現(xiàn)了所謂的?局部順序?的層次結(jié)構(gòu),包含更多的特定的??的層次。 一個角色可以包含一個權(quán)限,反之則不行。(譯者注:可理解為角色在上方,權(quán)限在下方,從上到下如果碰到權(quán)限那么再往下不能出現(xiàn)角色)

配置 RBAC

在開始定義授權(quán)數(shù)據(jù)和執(zhí)行存取檢查之前,需要先配置應(yīng)用組件 yii\base\Application::authManager 。 Yii 提供了兩套授權(quán)管理器: yii\rbac\PhpManager 和 yii\rbac\DbManager。前者使用 PHP 腳本存放授權(quán)數(shù)據(jù), 而后者使用數(shù)據(jù)庫存放授權(quán)數(shù)據(jù)。 如果你的應(yīng)用不要求大量的動態(tài)角色和權(quán)限管理, 你可以考慮使用前者。

使用?PhpManager

以下代碼展示使用 yii\rbac\PhpManager 時如何在應(yīng)用配置文件中配置?authManager

return [
    // ...
    'components' => [
        'authManager' => [
            'class' => 'yii\rbac\PhpManager',
        ],
        // ...
    ],
];

現(xiàn)在可以通過?\Yii::$app->authManager?訪問?authManager?。

yii\rbac\PhpManager 默認(rèn)將 RBAC 數(shù)據(jù)保存在?@app/rbac?目錄下的文件中。 如果權(quán)限層次數(shù)據(jù)在運行時會被修改,需確保WEB服務(wù)器進(jìn)程對該目錄和其中的文件有寫權(quán)限。

使用?DbManager

以下代碼展示使用 yii\rbac\DbManager 時如何在應(yīng)用配置文件中配置?authManager

return [
    // ...
    'components' => [
        'authManager' => [
            'class' => 'yii\rbac\DbManager',
        ],
        // ...
    ],
];

DbManager?使用4個數(shù)據(jù)庫表存放它的數(shù)據(jù):

  • yii\rbac\DbManager::$itemTable: 該表存放授權(quán)條目(譯者注:即角色和權(quán)限)。默認(rèn)表名為 "auth_item" 。
  • yii\rbac\DbManager::$itemChildTable: 該表存放授權(quán)條目的層次關(guān)系。默認(rèn)表名為 "auth_item_child"。
  • yii\rbac\DbManager::$assignmentTable: 該表存放授權(quán)條目對用戶的指派情況。默認(rèn)表名為 "auth_assignment"。
  • yii\rbac\DbManager::$ruleTable: 該表存放規(guī)則。默認(rèn)表名為 "auth_rule"。

繼續(xù)之前,你需要在數(shù)據(jù)庫中創(chuàng)建這些表。你可以使用存放在?@yii/rbac/migrations?目錄中的數(shù)據(jù)庫遷移文件來做這件事(譯者注:根據(jù)本人經(jīng)驗,最好是將授權(quán)數(shù)據(jù)初始化命令也寫到這個 RBAC 數(shù)據(jù)庫遷移文件中):

yii migrate --migrationPath=@yii/rbac/migrations

現(xiàn)在可以通過?\Yii::$app->authManager?訪問?authManager?。

建立授權(quán)數(shù)據(jù)

所有授權(quán)數(shù)據(jù)相關(guān)的任務(wù)如下:

  • 定義角色和權(quán)限;
  • 建立角色和權(quán)限的關(guān)系;
  • 定義規(guī)則;
  • 將規(guī)則與角色和權(quán)限作關(guān)聯(lián);
  • 指派角色給用戶。

根據(jù)授權(quán)的彈性需求,上述任務(wù)可用不同的方法完成。

如果你的權(quán)限層次結(jié)構(gòu)不會發(fā)生改變,而且你的用戶數(shù)是恒定的,你可以通過?authManager?提供的 API 創(chuàng)建一個?控制臺命令?一次性初始化授權(quán)數(shù)據(jù):

<?php
namespace app\commands;

use Yii;
use yii\console\Controller;

class RbacController extends Controller
{
    public function actionInit()
    {
        $auth = Yii::$app->authManager;

        // 添加 "createPost" 權(quán)限
        $createPost = $auth->createPermission('createPost');
        $createPost->description = 'Create a post';
        $auth->add($createPost);

        // 添加 "updatePost" 權(quán)限
        $updatePost = $auth->createPermission('updatePost');
        $updatePost->description = 'Update post';
        $auth->add($updatePost);

        // 添加 "author" 角色并賦予 "createPost" 權(quán)限
        $author = $auth->createRole('author');
        $auth->add($author);
        $auth->addChild($author, $createPost);

        // 添加 "admin" 角色并賦予 "updatePost" 
        // 和 "author" 權(quán)限
        $admin = $auth->createRole('admin');
        $auth->add($admin);
        $auth->addChild($admin, $updatePost);
        $auth->addChild($admin, $author);

        // 為用戶指派角色。其中 1 和 2 是由 IdentityInterface::getId() 返回的id (譯者注:user表的id)
        // 通常在你的 User 模型中實現(xiàn)這個函數(shù)。
        $auth->assign($author, 2);
        $auth->assign($admin, 1);
    }
}

在用?yii rbac/init?執(zhí)行了這個命令后,我們將得到下圖所示的層次結(jié)構(gòu):

作者可創(chuàng)建新貼,管理員可編輯帖子以及所有作者可做的事情。

如果你的應(yīng)用允許用戶注冊,你需要在注冊時給新用戶指派一次角色。例如, 在高級項目模板中,要讓所有注冊用戶成為作者,你需要如下例所示修改?frontend\models\SignupForm::signup()?方法:

public function signup()
{
    if ($this->validate()) {
        $user = new User();
        $user->username = $this->username;
        $user->email = $this->email;
        $user->setPassword($this->password);
        $user->generateAuthKey();
        $user->save(false);

        // 要添加以下三行代碼:
        $auth = Yii::$app->authManager;
        $authorRole = $auth->getRole('author');
        $auth->assign($authorRole, $user->getId());

        return $user;
    }

    return null;
}

對于有動態(tài)更改授權(quán)數(shù)據(jù)的復(fù)雜存取控制需求的,你可能需要使用?authManager?提供的 API 的開發(fā)用戶界面(例如:管理面板)。

使用規(guī)則 (Rules)

如前所述,規(guī)則給角色和權(quán)限增加額外的約束條件。規(guī)則是 yii\rbac\Rule 的派生類。 它需要實現(xiàn) yii\rbac\Rule::execute() 方法。在之前我們創(chuàng)建的層次結(jié)構(gòu)中,作者不能編輯自己的帖子,我們來修正這個問題。 首先我們需要一個規(guī)則來認(rèn)證當(dāng)前用戶是帖子的作者:

namespace app\rbac;

use yii\rbac\Rule;

/**
 * 檢查 authorID 是否和通過參數(shù)傳進(jìn)來的 user 參數(shù)相符
 */
class AuthorRule extends Rule
{
    public $name = 'isAuthor';

    /**
     * @param string|integer $user 用戶 ID.
     * @param Item $item 該規(guī)則相關(guān)的角色或者權(quán)限
     * @param array $params 傳給 ManagerInterface::checkAccess() 的參數(shù)
     * @return boolean 代表該規(guī)則相關(guān)的角色或者權(quán)限是否被允許
     */
    public function execute($user, $item, $params)
    {
        return isset($params['post']) ? $params['post']->createdBy == $user : false;
    }
}

上述規(guī)則檢查?post?是否是?$user?創(chuàng)建的。我們還要在之前的命令中 創(chuàng)建一個特別的權(quán)限?updateOwnPost?:

$auth = Yii::$app->authManager;

// 添加規(guī)則
$rule = new \app\rbac\AuthorRule;
$auth->add($rule);

// 添加 "updateOwnPost" 權(quán)限并與規(guī)則關(guān)聯(lián)
$updateOwnPost = $auth->createPermission('updateOwnPost');
$updateOwnPost->description = 'Update own post';
$updateOwnPost->ruleName = $rule->name;
$auth->add($updateOwnPost);

// "updateOwnPost" 權(quán)限將由 "updatePost" 權(quán)限使用
$auth->addChild($updateOwnPost, $updatePost);

// 允許 "author" 更新自己的帖子
$auth->addChild($author, $updateOwnPost);

現(xiàn)在我們得到如下層次結(jié)構(gòu):

存取檢查

授權(quán)數(shù)據(jù)準(zhǔn)備好后,存取檢查簡單到只需要一個方法調(diào)用 yii\rbac\ManagerInterface::checkAccess()。 因為大多數(shù)存取檢查都是針對當(dāng)前用戶而言,為方便起見, Yii 提供了一個快捷方法 yii\web\User::can(),可以如下例所示來使用:

if (\Yii::$app->user->can('createPost')) {
    // 建貼
}

如果當(dāng)前用戶是?ID=1?的 Jane ,我們從圖中的?createPost?開始,并試圖到達(dá)?Jane?。 (譯者注:參照圖中紅色路線所示的建貼授權(quán)流程)

為了檢查某用戶是否能更新帖子,我們需要傳遞一個額外的參數(shù),該參數(shù)是?AuthorRule?要用的:

if (\Yii::$app->user->can('updatePost', ['post' => $post])) {
    // 更新帖子
}

下圖所示為當(dāng)前用戶是 John 時所發(fā)生的事情:

我們從圖中的?updatePost?開始,經(jīng)過?updateOwnPost。為通過檢查,Authorrule?規(guī)則的?execute()?方法應(yīng)當(dāng)返回?true?。該方法從?can()?方法調(diào)用接收到?$params?參數(shù), 因此它的值是?['post' => $post]?。如果一切順利,我們會達(dá)到指派給 John 的author?角色。

對于 Jane 來說則更簡單,因為她是管理員:

使用默認(rèn)角色

所謂默認(rèn)角色就是?隱式?地指派給?所有?用戶的角色。不需要調(diào)用 yii\rbac\ManagerInterface::assign() 方法做顯示指派,并且授權(quán)數(shù)據(jù)中不包含指派信息。

默認(rèn)角色通常與一個規(guī)則關(guān)聯(lián),用以檢查該角色是否符合被檢查的用戶。

默認(rèn)角色常常用于已經(jīng)確立了一些角色的指派關(guān)系的應(yīng)用(譯者注:指派關(guān)系指的是應(yīng)用業(yè)務(wù)邏輯層面, 并非指授權(quán)數(shù)據(jù)的結(jié)構(gòu))。比如,一個應(yīng)用的 user 表中有一個?group?字段,代表用戶屬于哪個特權(quán)組。 如果每個特權(quán)組可以映射到 RBAC 的角色,你就可以采用默認(rèn)角色自動地為每個用戶指派一個 RBAC 角色。 讓我們用一個例子展示如何做到這一點。

假設(shè)在 user 表中,你有一個?group?字段,用 1 代表管理員組,用 2 表示作者組。 你規(guī)劃兩個 RBAC 角色?admin?和?author?分別對應(yīng)這兩個組的權(quán)限。 你可以這樣設(shè)置 RBAC 數(shù)據(jù),

namespace app\rbac;

use Yii;
use yii\rbac\Rule;

/**
 * 檢查是否匹配用戶的組
 */
class UserGroupRule extends Rule
{
    public $name = 'userGroup';

    public function execute($user, $item, $params)
    {
        if (!Yii::$app->user->isGuest) {
            $group = Yii::$app->user->identity->group;
            if ($item->name === 'admin') {
                return $group == 1;
            } elseif ($item->name === 'author') {
                return $group == 1 || $group == 2;
            }
        }
        return false;
    }
}

$auth = Yii::$app->authManager;

$rule = new \app\rbac\UserGroupRule;
$auth->add($rule);

$author = $auth->createRole('author');
$author->ruleName = $rule->name;
$auth->add($author);
// ... 添加$author角色的子項部分代碼 ... (譯者注:省略部分參照之前的控制臺命令)

$admin = $auth->createRole('admin');
$admin->ruleName = $rule->name;
$auth->add($admin);
$auth->addChild($admin, $author);
// ... 添加$admin角色的子項部分代碼 ... (譯者注:省略部分參照之前的控制臺命令)

注意,在上述代碼中,因為 "author" 作為 "admin" 的子角色,當(dāng)你實現(xiàn)這個規(guī)則的?execute()?方法時, 你也需要遵從這個層次結(jié)構(gòu)。這就是為何當(dāng)角色名為 "author" 的情況下(譯者注:$item->name就是角色名),?execute()?方法在組為 1 或者 2 時均要返回 true (意思是用戶屬于 "admin" 或者 "author" 組 )。

接下來,在配置?authManager?時指定 yii\rbac\BaseManager::$defaultRoles 選項(譯者注:在應(yīng)用配置文件中的組件部分配置):

return [
    // ...
    'components' => [
        'authManager' => [
            'class' => 'yii\rbac\PhpManager',
            'defaultRoles' => ['admin', 'author'],
        ],
        // ...
    ],
];

現(xiàn)在如果你執(zhí)行一個存取權(quán)限檢查, 判定該規(guī)則時,?admin?和?author?兩個角色都將會檢查。如果規(guī)則返回 true ,意思是角色符合當(dāng)前用戶?;谏鲜鲆?guī)則 的實現(xiàn),意味著如果某用戶的?group?值為 1 ,?admin?角色將賦予該用戶, 如果?group?值是 2 則將賦予author?角色。

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號