Yii 1.x 提供兩套授權機制:簡單的 CAccessControlFilter(ACF)與完整的 RBAC。對於中小型專案,ACF 配合資料庫角色欄位即可滿足分級存取需求,無需引入複雜 RBAC。本文詳細說明如何擴充 `CWebUser`、修改設定、改寫 `accessRules()` 實現「管理員/一般用戶」兩級權限控制。
Gii 生成的 Controller 預設包含:
public function filters() {
return [
'accessControl', // 啟用 ACF
'postOnly + delete',
];
}
public function accessRules() {
return [
['allow', 'actions' => ['index','view'], 'users' => ['*']],
['allow', 'actions' => ['create','update'], 'users' => ['@']],
['allow', 'actions' => ['admin','delete'], 'users' => ['admin']], // 硬編碼用戶名
['deny', 'users' => ['*']],
];
}
缺點:`users => ['admin']` 硬編碼用戶名,無法動態對應資料庫角色。
檔案:`protected/components/WebUser.php`
<?php
class WebUser extends CWebUser {
private $_model = null;
// 取得 first_name(示例)
public function getFirstName() {
$user = $this->loadUser(Yii::app()->user->id);
return $user ? $user->first_name : '';
}
// 核心:判斷是否為管理員
public function isAdmin(): bool {
$user = $this->loadUser(Yii::app()->user->id);
return $user !== null && $user->role === '管理員'; // 對應資料庫 role 欄位
}
protected function loadUser($id = null) {
if ($this->_model === null && $id !== null) {
$this->_model = User::model()->findByPk($id);
}
return $this->_model;
}
}
檔案:`protected/config/main.php`
'components' => [
'user' => [
'allowAutoLogin' => true,
'class' => 'WebUser', // 關鍵:指定自定義類別
],
// ...
],
public function accessRules() {
return [
['allow', 'actions' => ['index','view'], 'users' => ['*']],
['allow', 'actions' => ['create','update'], 'users' => ['@']],
// 使用 expression 動態評估 PHP 運算式
['allow', 'actions' => ['admin','delete'], 'expression' => 'Yii::app()->user->isAdmin()'],
['deny', 'users' => ['*']],
];
}
關鍵點: `expression` 允許任意 PHP 運算式,回傳 `true` 即通過。此處呼叫 `Yii::app()->user->isAdmin()`,自動委派給擴充的 `WebUser::isAdmin()`。
public function hasRole(string $role): bool {
$user = $this->loadUser(Yii::app()->user->id);
return $user !== null && $user->role === $role;
}
public function hasAnyRole(array $roles): bool {
$user = $this->loadUser(Yii::app()->user->id);
return $user !== null && in_array($user->role, $roles, true);
}
public function accessRules() {
return [
['allow', 'actions' => ['index','view'], 'users' => ['*']],
['allow', 'actions' => ['create','update'], 'users' => ['@']],
// 僅管理員與編輯可存取 admin/delete
['allow', 'actions' => ['admin','delete'], 'expression' => 'Yii::app()->user->hasAnyRole(["管理員","編輯"])'],
['deny', 'users' => ['*']],
];
}
`WebUser::loadUser()` 已使用實例變數 `$_model` 快取,同一請求內多次呼叫 `isAdmin()`/`hasRole()` 只查詢一次資料庫。若需跨請求快取,可整合 `CCache`:
protected function loadUser($id = null) {
if ($this->_model === null && $id !== null) {
$cacheKey = "user_{$id}";
if (($this->_model = Yii::app()->cache->get($cacheKey)) === false) {
$this->_model = User::model()->findByPk($id);
if ($this->_model) Yii::app()->cache->set($cacheKey, $this->_model, 300); // 5分鐘
}
}
return $this->_model;
}
| 問題 | 原因 | 解決 |
|---|---|---|
| expression 不生效 | 拼寫錯誤 / 未返回布林值 | 確認運算式回傳 true/false,使用 `var_dump` 除錯 |
| 角色變更未即時生效 | WebUser 快取舊模型 / Session 未更新 | 登出重登、或實作 `afterSave` 清除快取 |
| 超級管理員無法存取 | 資料庫 role 值不匹配(如 'admin' vs '管理員') | 統一角色常數定義,避免魔術字串 |
| API 無狀態請求無法使用 | ACF 依賴 Session/Cookie | API 改用 Token 驗證 + 自定義 Filter |
未達上述條件,ACF + `expression` 彈性足夠,維護成本遠低於 RBAC。
Yii `CAccessControlFilter` 配合 `CWebUser` 擴充與 `expression` 運算式,能以極低複雜度實現基於資料庫角色的分級存取控制。核心在於:將授權邏輯從 Controller 遷移至 User 元件,Controller 僅宣告規則,業務判斷集中在 `WebUser`。此模式符合單一職責原則,且平滑過渡至未來 RBAC 遷移(只需替換 `isAdmin()` 實作)。