# 授權
授權是指驗證用戶是否允許做某件事的過程。Yii提供兩種授權方法: 存取控制過濾器(ACF)和基于角色的存取控制(RBAC)。
## 存取控制過濾器
存取控制過濾器(ACF)是一種通過 yii\filters\AccessControl 類來實現的簡單授權方法, 非常適用于僅需要簡單的存取控制的應用。正如其名稱所指,ACF 是一個種行動(action)過濾器?[filter](http://www.yiichina.com/doc/guide/2.0/structure-filters),可在控制器或者模塊中使用。當一個用戶請求一個 action 時, ACF會檢查 yii\filters\AccessControl::rules 列表,判斷該用戶是否允許執 行所請求的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 應當只 對?`login`,?`logout`?和?`signup`?方法起作用。所有其它的?`site`?控制器中的方法不受存取控制的限制。?`rules`?選項列出了 yii\filters\AccessRule,解讀如下:
* 允許所有訪客(還未經認證的用戶)執行?`login`?和?`signup`?操作。?`roles`?選項包含的問號?`?`?是一個特殊的標識,代表”訪客用戶”。
* 允許已認證用戶執行?`logout`?操作。`@`是另一個特殊標識, 代表”已認證用戶”。
ACF 自頂向下逐一檢查存取規則,直到找到一個與當前 欲執行的操作相符的規則。 然后該匹配規則中的?`allow`?選項的值用于判定該用戶是否獲得授權。如果沒有找到匹配的規則, 意味著該用戶沒有獲得授權。(譯者注:?`only`?中沒有列出的操作,將無條件獲得授權)
當 ACF 判定一個用戶沒有獲得執行當前操作的授權時,它的默認處理是:
* 如果該用戶是訪客,將調用 yii\web\User::loginRequired() 將用戶的瀏覽器重定向到登錄頁面。
* 如果該用戶是已認證用戶,將拋出一個 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 來創建自定義的存取規則類。
* yii\filters\AccessRule::allow: 指定該規則是 "允許" 還是 "拒絕" 。(譯者注:true是允許,false是拒絕)
* yii\filters\AccessRule::actions:指定該規則用于匹配哪些操作。 它的值應該是操作方法的ID數組。匹配比較是大小寫敏感的。如果該選項為空,或者不使用該選項, 意味著當前規則適用于所有的操作。
* yii\filters\AccessRule::controllers:指定該規則用于匹配哪些控制器。 它的值應為控制器ID數組。匹配比較是大小寫敏感的。如果該選項為空,或者不使用該選項, 則意味著當前規則適用于所有的操作。(譯者注:這個選項一般是在控制器的自定義父類中使用才有意義)
* yii\filters\AccessRule::roles:指定該規則用于匹配哪些用戶角色。 系統自帶兩個特殊的角色,通過 yii\web\User::isGuest 來判斷:
* `?`: 用于匹配訪客用戶 (未經認證)
* `@`: 用于匹配已認證用戶
使用其他角色名時,將觸發調用 yii\web\User::can(),這時要求 RBAC 的支持 (在下一節中闡述)。 如果該選項為空或者不使用該選項,意味著該規則適用于所有角色。
* yii\filters\AccessRule::ips:指定該規則用于匹配哪些 yii\web\Request::userIP 。 IP 地址可在其末尾包含通配符?`*`?以匹配一批前綴相同的IP地址。 例如,`192.168.*`?匹配所有?`192.168.`?段的IP地址。 如果該選項為空或者不使用該選項,意味著該規則適用于所有角色。
* yii\filters\AccessRule::verbs:指定該規則用于匹配哪種請求方法(例如`GET`,`POST`)。 這里的匹配大小寫不敏感。
* yii\filters\AccessRule::matchCallback:指定一個PHP回調函數用于 判定該規則是否滿足條件。(譯者注:此處的回調函數是匿名函數)
* yii\filters\AccessRule::denyCallback: 指定一個PHP回調函數, 當這個規則不滿足條件時該函數會被調用。(譯者注:此處的回調函數是匿名函數)
以下例子展示了如何使用?`matchCallback`?選項, 可使你設計任意的訪問權限檢查邏輯:
~~~
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';
}
],
],
],
];
}
// 匹配的回調函數被調用了!這個頁面只有每年的10月31號能訪問(譯者注:原文在這里說該方法是回調函數不確切,讀者不要和 `matchCallback` 的值即匿名的回調函數混淆理解)。
public function actionSpecialCallback()
{
return $this->render('happy-halloween');
}
}
~~~
## 基于角色的存取控制 (RBAC)
基于角色的存取控制 (RBAC) 提供了一個簡單而強大的集中式存取控制機制。 詳細的關于 RBAC 和諸多傳統的存取控制方案對比的詳情,請參閱?[Wikipedia](http://en.wikipedia.org/wiki/Role-based_access_control)。
Yii 實現了通用的分層的 RBAC,遵循的模型是?[NIST RBAC model](http://csrc.nist.gov/rbac/sandhu-ferraiolo-kuhn-00.pdf). 它通過 yii\rbac\ManagerInterface?[application component](http://www.yiichina.com/doc/guide/2.0/structure-application-components)?提供 RBAC 功能。
使用 RBAC 涉及到兩部分工作。第一部分是建立授權數據, 而第二部分是使用這些授權數據在需要的地方執行檢查。
為方便后面的講述,這里先介紹一些 RBAC 的基本概念。
### 基本概念
角色是?*權限*?的集合 (例如:建貼、改貼)。一個角色 可以指派給一個或者多個用戶。要檢查某用戶是否有一個特定的權限, 系統會檢查該包含該權限的角色是否指派給了該用戶。
可以用一個規則?*rule*?與一個角色或者權限關聯。一個規則用一段代碼代表, 規則的執行是在檢查一個用戶是否滿足這個角色或者權限時進行的。例如,"改帖" 的權限 可以使用一個檢查該用戶是否是帖子的創建者的規則。權限檢查中,如果該用戶 不是帖子創建者,那么他(她)將被認為不具有 "改帖"的權限。
角色和權限都可以按層次組織。特定情況下,一個角色可能由其他角色或權限構成, 而權限又由其他的權限構成。Yii 實現了所謂的?*局部順序*?的層次結構,包含更多的特定的?*樹*?的層次。 一個角色可以包含一個權限,反之則不行。(譯者注:可理解為角色在上方,權限在下方,從上到下如果碰到權限那么再往下不能出現角色)
### 配置 RBAC
在開始定義授權數據和執行存取檢查之前,需要先配置應用組件 yii\base\Application::authManager 。 Yii 提供了兩套授權管理器: yii\rbac\PhpManager 和 yii\rbac\DbManager。前者使用 PHP 腳本存放授權數據, 而后者使用數據庫存放授權數據。 如果你的應用不要求大量的動態角色和權限管理, 你可以考慮使用前者。
#### 使用?`PhpManager`
以下代碼展示使用 yii\rbac\PhpManager 時如何在應用配置文件中配置?`authManager`:
~~~
return [
// ...
'components' => [
'authManager' => [
'class' => 'yii\rbac\PhpManager',
],
// ...
],
];
~~~
現在可以通過?`\Yii::$app->authManager`?訪問?`authManager`?。
yii\rbac\PhpManager 默認將 RBAC 數據保存在?`@app/rbac`?目錄下的文件中。 如果權限層次數據在運行時會被修改,需確保WEB服務器進程對該目錄和其中的文件有寫權限。
#### 使用?`DbManager`
以下代碼展示使用 yii\rbac\DbManager 時如何在應用配置文件中配置?`authManager`:
~~~
return [
// ...
'components' => [
'authManager' => [
'class' => 'yii\rbac\DbManager',
],
// ...
],
];
~~~
`DbManager`?使用4個數據庫表存放它的數據:
* yii\rbac\DbManager::$itemTable: 該表存放授權條目(譯者注:即角色和權限)。默認表名為 "auth_item" 。
* yii\rbac\DbManager::$itemChildTable: 該表存放授權條目的層次關系。默認表名為 "auth_item_child"。
* yii\rbac\DbManager::$assignmentTable: 該表存放授權條目對用戶的指派情況。默認表名為 "auth_assignment"。
* yii\rbac\DbManager::$ruleTable: 該表存放規則。默認表名為 "auth_rule"。
繼續之前,你需要在數據庫中創建這些表。你可以使用存放在?`@yii/rbac/migrations`?目錄中的數據庫遷移文件來做這件事(譯者注:根據本人經驗,最好是將授權數據初始化命令也寫到這個 RBAC 數據庫遷移文件中):
`yii migrate --migrationPath=@yii/rbac/migrations`
現在可以通過?`\Yii::$app->authManager`?訪問?`authManager`?。
### 建立授權數據
所有授權數據相關的任務如下:
* 定義角色和權限;
* 建立角色和權限的關系;
* 定義規則;
* 將規則與角色和權限作關聯;
* 指派角色給用戶。
根據授權的彈性需求,上述任務可用不同的方法完成。
如果你的權限層次結構不會發生改變,而且你的用戶數是恒定的,你可以通過?`authManager`?提供的 API 創建一個?[控制臺命令](http://www.yiichina.com/doc/guide/2.0/tutorial-console#create-command)?一次性初始化授權數據:
~~~
<?php
namespace app\commands;
use Yii;
use yii\console\Controller;
class RbacController extends Controller
{
public function actionInit()
{
$auth = Yii::$app->authManager;
// 添加 "createPost" 權限
$createPost = $auth->createPermission('createPost');
$createPost->description = 'Create a post';
$auth->add($createPost);
// 添加 "updatePost" 權限
$updatePost = $auth->createPermission('updatePost');
$updatePost->description = 'Update post';
$auth->add($updatePost);
// 添加 "author" 角色并賦予 "createPost" 權限
$author = $auth->createRole('author');
$auth->add($author);
$auth->addChild($author, $createPost);
// 添加 "admin" 角色并賦予 "updatePost"
// 和 "author" 權限
$admin = $auth->createRole('admin');
$auth->add($admin);
$auth->addChild($admin, $updatePost);
$auth->addChild($admin, $author);
// 為用戶指派角色。其中 1 和 2 是由 IdentityInterface::getId() 返回的id (譯者注:user表的id)
// 通常在你的 User 模型中實現這個函數。
$auth->assign($author, 2);
$auth->assign($admin, 1);
}
}
~~~
在用?`yii rbac/init`?執行了這個命令后,我們將得到下圖所示的層次結構:

作者可創建新貼,管理員可編輯帖子以及所有作者可做的事情。
如果你的應用允許用戶注冊,你需要在注冊時給新用戶指派一次角色。例如, 在高級項目模板中,要讓所有注冊用戶成為作者,你需要如下例所示修改?`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;
}
~~~
對于有動態更改授權數據的復雜存取控制需求的,你可能需要使用?`authManager`?提供的 API 的開發用戶界面(例如:管理面板)。
### 使用規則 (Rules)
如前所述,規則給角色和權限增加額外的約束條件。規則是 yii\rbac\Rule 的派生類。 它需要實現 yii\rbac\Rule::execute() 方法。在之前我們創建的層次結構中,作者不能編輯自己的帖子,我們來修正這個問題。 首先我們需要一個規則來認證當前用戶是帖子的作者:
~~~
namespace app\rbac;
use yii\rbac\Rule;
/**
* 檢查 authorID 是否和通過參數傳進來的 user 參數相符
*/
class AuthorRule extends Rule
{
public $name = 'isAuthor';
/**
* @param string|integer $user 用戶 ID.
* @param Item $item 該規則相關的角色或者權限
* @param array $params 傳給 ManagerInterface::checkAccess() 的參數
* @return boolean 代表該規則相關的角色或者權限是否被允許
*/
public function execute($user, $item, $params)
{
return isset($params['post']) ? $params['post']->createdBy == $user : false;
}
}
~~~
上述規則檢查?`post`?是否是?`$user`?創建的。我們還要在之前的命令中 創建一個特別的權限?`updateOwnPost`?:
~~~
$auth = Yii::$app->authManager;
// 添加規則
$rule = new \app\rbac\AuthorRule;
$auth->add($rule);
// 添加 "updateOwnPost" 權限并與規則關聯
$updateOwnPost = $auth->createPermission('updateOwnPost');
$updateOwnPost->description = 'Update own post';
$updateOwnPost->ruleName = $rule->name;
$auth->add($updateOwnPost);
// "updateOwnPost" 權限將由 "updatePost" 權限使用
$auth->addChild($updateOwnPost, $updatePost);
// 允許 "author" 更新自己的帖子
$auth->addChild($author, $updateOwnPost);
~~~
現在我們得到如下層次結構:

### 存取檢查
授權數據準備好后,存取檢查簡單到只需要一個方法調用 yii\rbac\ManagerInterface::checkAccess()。 因為大多數存取檢查都是針對當前用戶而言,為方便起見, Yii 提供了一個快捷方法 yii\web\User::can(),可以如下例所示來使用:
~~~
if (\Yii::$app->user->can('createPost')) {
// 建貼
}
~~~
如果當前用戶是?`ID=1`?的 Jane ,我們從圖中的?`createPost`?開始,并試圖到達?`Jane`?。 (譯者注:參照圖中紅色路線所示的建貼授權流程)

為了檢查某用戶是否能更新帖子,我們需要傳遞一個額外的參數,該參數是?`AuthorRule`?要用的:
~~~
if (\Yii::$app->user->can('updatePost', ['post' => $post])) {
// 更新帖子
}
~~~
下圖所示為當前用戶是 John 時所發生的事情:

我們從圖中的?`updatePost`?開始,經過?`updateOwnPost`。為通過檢查,`Authorrule`?規則的?`execute()`?方法應當返回?`true`?。該方法從?`can()`?方法調用接收到?`$params`?參數, 因此它的值是?`['post' => $post]`?。如果一切順利,我們會達到指派給 John 的`author`?角色。
對于 Jane 來說則更簡單,因為她是管理員:

### 使用默認角色
所謂默認角色就是?*隱式*?地指派給?*所有*?用戶的角色。不需要調用 yii\rbac\ManagerInterface::assign() 方法做顯示指派,并且授權數據中不包含指派信息。
默認角色通常與一個規則關聯,用以檢查該角色是否符合被檢查的用戶。
默認角色常常用于已經確立了一些角色的指派關系的應用(譯者注:指派關系指的是應用業務邏輯層面, 并非指授權數據的結構)。比如,一個應用的 user 表中有一個?`group`?字段,代表用戶屬于哪個特權組。 如果每個特權組可以映射到 RBAC 的角色,你就可以采用默認角色自動地為每個用戶指派一個 RBAC 角色。 讓我們用一個例子展示如何做到這一點。
假設在 user 表中,你有一個?`group`?字段,用 1 代表管理員組,用 2 表示作者組。 你規劃兩個 RBAC 角色?`admin`?和?`author`?分別對應這兩個組的權限。 你可以這樣設置 RBAC 數據,
~~~
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" 的子角色,當你實現這個規則的?`execute()`?方法時, 你也需要遵從這個層次結構。這就是為何當角色名為 "author" 的情況下(譯者注:`$item->name`就是角色名),?`execute()`?方法在組為 1 或者 2 時均要返回 true (意思是用戶屬于 "admin" 或者 "author" 組 )。
接下來,在配置?`authManager`?時指定 yii\rbac\BaseManager::$defaultRoles 選項(譯者注:在應用配置文件中的組件部分配置):
~~~
return [
// ...
'components' => [
'authManager' => [
'class' => 'yii\rbac\PhpManager',
'defaultRoles' => ['admin', 'author'],
],
// ...
],
];
~~~
現在如果你執行一個存取權限檢查, 判定該規則時,?`admin`?和?`author`?兩個角色都將會檢查。如果規則返回 true ,意思是角色符合當前用戶。基于上述規則 的實現,意味著如果某用戶的?`group`?值為 1 ,?`admin`?角色將賦予該用戶, 如果?`group`?值是 2 則將賦予`author`?角色。
- 介紹(Introduction)
- 關于 Yii(About Yii)
- 從 Yii 1.1 升級(Upgrading from Version 1.1)
- 入門(Getting Started)
- 安裝 Yii(Installing Yii)
- 運行應用(Running Applications)
- 第一次問候(Saying Hello)
- 使用 Forms(Working with Forms)
- 玩轉 Databases(Working with Databases)
- 用 Gii 生成代碼(Generating Code with Gii)
- 更上一層樓(Looking Ahead)
- 應用結構(Application Structure)
- 結構概述(Overview)
- 入口腳本(Entry Scripts)
- 應用(Applications)
- 應用組件(Application Components)
- 控制器(Controllers)
- 模型(Models)
- 視圖(Views)
- 模塊(Modules)
- 過濾器(Filters)
- 小部件(Widgets)
- 前端資源(Assets)
- 擴展(Extensions)
- 請求處理(Handling Requests)
- 運行概述(Overview)
- 引導(Bootstrapping)
- 路由引導與創建 URL(Routing and URL Creation)
- 請求(Requests)
- 響應(Responses)
- Sessions and Cookies
- 錯誤處理(Handling Errors)
- 日志(Logging)
- 關鍵概念(Key Concepts)
- 組件(Components)
- 屬性(Properties)
- 事件(Events)
- 行為(Behaviors)
- 配置(Configurations)
- 別名(Aliases)
- 類自動加載(Class Autoloading)
- 服務定位器(Service Locator)
- 依賴注入容器(Dependency Injection Container)
- 配合數據庫工作(Working with Databases)
- 數據庫訪問(Data Access Objects): 數據庫連接、基本查詢、事務和模式操作
- 查詢生成器(Query Builder): 使用簡單抽象層查詢數據庫
- 活動記錄(Active Record): 活動記錄對象關系映射(ORM),檢索和操作記錄、定義關聯關系
- 數據庫遷移(Migrations): 在團體開發中對你的數據庫使用版本控制
- Sphinx
- Redis
- MongoDB
- ElasticSearch
- 接收用戶數據(Getting Data from Users)
- 創建表單(Creating Forms)
- 輸入驗證(Validating Input)
- 文件上傳(Uploading Files)
- 收集列表輸入(Collecting Tabular Input)
- 多模型同時輸入(Getting Data for Multiple Models)
- 顯示數據(Displaying Data)
- 格式化輸出數據(Data Formatting)
- 分頁(Pagination)
- 排序(Sorting)
- 數據提供器(Data Providers)
- 數據小部件(Data Widgets)
- 操作客戶端腳本(Working with Client Scripts)
- 主題(Theming)
- 安全(Security)
- 認證(Authentication)
- 授權(Authorization)
- 處理密碼(Working with Passwords)
- 客戶端認證(Auth Clients)
- 安全領域的最佳實踐(Best Practices)
- 緩存(Caching)
- 概述(Overview)
- 數據緩存(Data Caching)
- 片段緩存(Fragment Caching)
- 分頁緩存(Page Caching)
- HTTP 緩存(HTTP Caching)
- RESTful Web 服務
- 快速入門(Quick Start)
- 資源(Resources)
- 控制器(Controllers)
- 路由(Routing)
- 格式化響應(Response Formatting)
- 授權驗證(Authentication)
- 速率限制(Rate Limiting)
- 版本化(Versioning)
- 錯誤處理(Error Handling)
- 開發工具(Development Tools)
- 調試工具欄和調試器(Debug Toolbar and Debugger)
- 使用 Gii 生成代碼(Generating Code using Gii)
- TBD 生成 API 文檔(Generating API Documentation)
- 測試(Testing)
- 概述(Overview)
- 搭建測試環境(Testing environment setup)
- 單元測試(Unit Tests)
- 功能測試(Functional Tests)
- 驗收測試(Acceptance Tests)
- 測試夾具(Fixtures)
- 高級專題(Special Topics)
- 高級應用模版(Advanced Project Template)
- 從頭構建自定義模版(Building Application from Scratch)
- 控制臺命令(Console Commands)
- 核心驗證器(Core Validators)
- 國際化(Internationalization)
- 收發郵件(Mailing)
- 性能優化(Performance Tuning)
- 共享主機環境(Shared Hosting Environment)
- 模板引擎(Template Engines)
- 集成第三方代碼(Working with Third-Party Code)
- 小部件(Widgets)
- Bootstrap 小部件(Bootstrap Widgets)
- jQuery UI 小部件(jQuery UI Widgets)
- 助手類(Helpers)
- 助手一覽(Overview)
- Array 助手(ArrayHelper)
- Html 助手(Html)
- Url 助手(Url)