摘要:認(rèn)證事件類在登錄和注銷流程引發(fā)一些事件。成功注銷后引發(fā)。提供兩種授權(quán)方法存取控制過濾器和基于角色的存取控制。允許已認(rèn)證用戶執(zhí)行操作。指定一個回調(diào)函數(shù)用于判定該規(guī)則是否滿足條件。
簡述
在程序開發(fā)過程中,往往都不能忽視安全問題,無論你的框架有多么完美,都會有破綻,所以完善自己的系統(tǒng),從程序開發(fā)的安全角度去思考問題,把一切潛在的危機扼殺在搖籃中。
認(rèn)證(Authentication)認(rèn)證是鑒定用戶身份的過程。
它通常使用一個標(biāo)識符 (如用戶名或電子郵件地址)和一個加密令牌(比如密碼或者存取令牌)來 鑒別用戶身份。
認(rèn)證是登錄功能的基礎(chǔ)。
Yii提供了一個認(rèn)證框架,它連接了不同的組件以支持登錄。欲使用這個框架, 你主要需要做以下工作:
設(shè)置用戶組件 yiiwebUser ;
創(chuàng)建一個類實現(xiàn) yiiwebIdentityInterface 接口。
配置 yiiwebUser用戶組件 yiiwebUser 用來管理用戶的認(rèn)證狀態(tài)。
這需要你 指定一個含有實際認(rèn)證邏輯的認(rèn)證類 yiiwebUser::identityClass。
在以下web應(yīng)用的配置項中,將用戶用戶組件 yiiwebUser 的 認(rèn)證類 yiiwebUser::identityClass 配置成 模型類 appmodelsUser。
return [ "components" => [ "user" => [ "identityClass" => "appmodelsUser", ], ], ];認(rèn)證接口 yiiwebIdentityInterface 的實現(xiàn)
認(rèn)證類 yiiwebUser::identityClass 必須實現(xiàn)包含以下方法的 認(rèn)證接口 yiiwebIdentityInterface:
yiiwebIdentityInterface::findIdentity():根據(jù)指定的用戶ID查找 認(rèn)證模型類的實例,當(dāng)你需要使用session來維持登錄狀態(tài)的時候會用到這個方法。
yiiwebIdentityInterface::findIdentityByAccessToken():根據(jù)指定的存取令牌查找 認(rèn)證模型類的實例,該方法用于 通過單個加密令牌認(rèn)證用戶的時候(比如無狀態(tài)的RESTful應(yīng)用)。
yiiwebIdentityInterface::getId():獲取該認(rèn)證實例表示的用戶的ID。
yiiwebIdentityInterface::getAuthKey():獲取基于 cookie 登錄時使用的認(rèn)證密鑰。 認(rèn)證密鑰儲存在 cookie 里并且將來會與服務(wù)端的版本進行比較以確保 cookie的有效性。
yiiwebIdentityInterface::validateAuthKey() :是基于 cookie 登錄密鑰的 驗證的邏輯的實現(xiàn)。
用不到的方法可以空著,例如,你的項目只是一個 無狀態(tài)的 RESTful 應(yīng)用,只需實現(xiàn) yiiwebIdentityInterface::findIdentityByAccessToken() 和 yiiwebIdentityInterface::getId() ,其他的方法的函數(shù)體留空即可。
下面的例子是一個通過結(jié)合了 user 數(shù)據(jù)表的 AR 模型 Active Record 實現(xiàn)的一個認(rèn)證類 yiiwebUser::identityClass。
$token]); } /** * @return int|string 當(dāng)前用戶ID */ public function getId() { return $this->id; } /** * @return string 當(dāng)前用戶的(cookie)認(rèn)證密鑰 */ public function getAuthKey() { return $this->auth_key; } /** * @param string $authKey * @return boolean if auth key is valid for current user */ public function validateAuthKey($authKey) { return $this->getAuthKey() === $authKey; } }
如上所述,如果你的應(yīng)用利用 cookie 登錄, 你只需要實現(xiàn) getAuthKey() 和 validateAuthKey() 方法。這樣的話,你可以使用下面的代碼在 user 表中生成和存儲每個用戶的認(rèn)證密鑰。
class User extends ActiveRecord implements IdentityInterface{ ...... public function beforeSave($insert) { if (parent::beforeSave($insert)) { if ($this->isNewRecord) { $this->auth_key = Yii::$app->security->generateRandomString(); } return true; } return false; } }
注意:不要混淆 user 認(rèn)證類和用戶組件 yiiwebUser。前者是實現(xiàn) 認(rèn)證邏輯的類,通常用關(guān)聯(lián)了 持久性存儲的用戶信息的AR模型 Active Record 實現(xiàn)。后者是負(fù)責(zé)管理用戶認(rèn)證狀態(tài)的 應(yīng)用組件。
使用用戶組件 yiiwebUser在 user 應(yīng)用組件方面,你主要用到 yiiwebUser 。
你可以使用表達式 Yii::$app->user->identity 檢測當(dāng)前用戶身份。它返回 一個表示當(dāng)前登錄用戶的認(rèn)證類 yiiwebUser::identityClass 的實例, 未認(rèn)證用戶(游客)則返回 null。
下面的代碼展示了如何從 yiiwebUser 獲取其他認(rèn)證相關(guān)信息:
// 當(dāng)前用戶的身份實例。未認(rèn)證用戶則為 Null 。 $identity = Yii::$app->user->identity; // 當(dāng)前用戶的ID。 未認(rèn)證用戶則為 Null 。 $id = Yii::$app->user->id; // 判斷當(dāng)前用戶是否是游客(未認(rèn)證的) $isGuest = Yii::$app->user->isGuest;
你可以使用下面的代碼登錄用戶:
// 使用指定用戶名獲取用戶身份實例。 // 請注意,如果需要的話您可能要檢驗密碼 $identity = User::findOne(["username" => $username]); // 登錄用戶 Yii::$app->user->login($identity); yiiwebUser::login() 方法將當(dāng)前用戶的身份登記到 yiiwebUser。
如果 session 設(shè)置為 yiiwebUser::enableSession,則使用 session 記錄用戶身份,用戶的 認(rèn)證狀態(tài)將在整個會話中得以維持。
如果開啟自動登錄 yiiwebUser::enableAutoLogin 則基于 cookie 登錄(如:記住登錄狀態(tài)),它將使用 cookie 保存用戶身份,這樣 只要 cookie 有效就可以恢復(fù)登錄狀態(tài)。
為了使用 cookie 登錄,你需要在應(yīng)用配置文件中將 yiiwebUser::enableAutoLogin 設(shè)為 true。 你還需要在 yiiwebUser::login() 方法中 傳遞有效期(記住登錄狀態(tài)的時長)參數(shù)。
注銷用戶:
Yii::$app->user->logout();
請注意,啟用 session 時注銷用戶才有意義。該方法將從內(nèi)存和 session 中 同時清理用戶認(rèn)證狀態(tài)。
默認(rèn)情況下,它還會注銷所有的 用戶會話數(shù)據(jù)。
如果你希望保留這些會話數(shù)據(jù),可以換成 Yii::$app->user->logout(false) 。認(rèn)證事件
yiiwebUser 類在登錄和注銷流程引發(fā)一些事件。
1、yiiwebUser::EVENT_BEFORE_LOGIN:在登錄 yiiwebUser::login() 時引發(fā)。 如果事件句柄將事件對象的 yiiwebUserEvent::isValid 屬性設(shè)為 false, 登錄流程將會被取消。 2、yiiwebUser::EVENT_AFTER_LOGIN:登錄成功后引發(fā)。 3、yiiwebUser::EVENT_BEFORE_LOGOUT:注銷 yiiwebUser::logout() 前引發(fā)。 如果事件句柄將事件對象的 yiiwebUserEvent::isValid 屬性設(shè)為 false, 注銷流程將會被取消。 4、yiiwebUser::EVENT_AFTER_LOGOUT:成功注銷后引發(fā)。
你可以通過響應(yīng)這些事件來實現(xiàn)一些類似登錄統(tǒng)計、在線人數(shù)統(tǒng)計的功能。例如, 在登錄后 yiiwebUser::EVENT_AFTER_LOGIN 的處理程序,你可以將用戶的登錄時間和IP記錄到 user 表中。
授權(quán)(Authorization)授權(quán)是指驗證用戶是否允許做某件事的過程。
Yii提供兩種授權(quán)方法: 存取控制過濾器(ACF)和基于角色的存取控制(RBAC)。
存取控制過濾器存取控制過濾器(ACF)是一種通過 yiifiltersAccessControl 類來實現(xiàn)的簡單授權(quán)方法, 非常適用于僅需要簡單的存取控制的應(yīng)用。
正如其名稱所指,ACF 是一個種行動(action)過濾器 filter,可在控制器或者模塊中使用。當(dāng)一個用戶請求一個 action 時, ACF會檢查 yiifiltersAccessControl::rules 列表,判斷該用戶是否允許執(zhí) 行所請求的action。(譯者注: action 在本文中視情況翻譯為行動、操作、方法等)
下述代碼展示如何在 site 控制器中使用 ACF:
use yiiwebController; use yiifiltersAccessControl; 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 選項列出了 yiifiltersAccessRule,解讀如下:
允許所有訪客(還未經(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)用 yiiwebUser::loginRequired() 將用戶的瀏覽器重定向到登錄頁面。
如果該用戶是已認(rèn)證用戶,將拋出一個 yiiwebForbiddenHttpException 異常。
你可以通過配置 yiifiltersAccessControl::denyCallback 屬性定制該行為:
[ "class" => AccessControl::className(), ... "denyCallback" => function ($rule, $action) { throw new Exception("You are not allowed to access this page"); } ]
yiifiltersAccessRule 支持很多的選項。
下列是所支持選項的總覽:你可以派生 yiifiltersAccessRule 來創(chuàng)建自定義的存取規(guī)則類。
* yiifiltersAccessRule::allow: 指定該規(guī)則是 "允許" 還是 "拒絕" 。(譯者注:true是允許,false是拒絕) * yiifiltersAccessRule::actions:指定該規(guī)則用于匹配哪些操作。 它的值應(yīng)該是操作方法的ID數(shù)組。匹配比較是大小寫敏感的。如果該選項為空,或者不使用該選項, 意味著當(dāng)前規(guī)則適用于所有的操作。 1、yiifiltersAccessRule::controllers:指定該規(guī)則用于匹配哪些控制器。 它的值應(yīng)為控制器ID數(shù)組。匹配比較是大小寫敏感的。如果該選項為空,或者不使用該選項, 則意味著當(dāng)前規(guī)則適用于所有的操作。(譯者注:這個選項一般是在控制器的自定義父類中使用才有意義) 2、yiifiltersAccessRule::roles:指定該規(guī)則用于匹配哪些用戶角色。 系統(tǒng)自帶兩個特殊的角色,通過 yiiwebUser::isGuest 來判斷: * ?: 用于匹配訪客用戶 (未經(jīng)認(rèn)證) * @: 用于匹配已認(rèn)證用戶 3、使用其他角色名時,將觸發(fā)調(diào)用 yiiwebUser::can(),這時要求 RBAC 的支持 (在下一節(jié)中闡述)。 如果該選項為空或者不使用該選項,意味著該規(guī)則適用于所有角色。 4、yiifiltersAccessRule::ips:指定該規(guī)則用于匹配哪些 yiiwebRequest::userIP 。 IP 地址可在其末尾包含通配符 * 以匹配一批前綴相同的IP地址。 例如,192.168.* 匹配所有 192.168. 段的IP地址。 如果該選項為空或者不使用該選項,意味著該規(guī)則適用于所有角色。 5、yiifiltersAccessRule::verbs:指定該規(guī)則用于匹配哪種請求方法(例如GET,POST)。 這里的匹配大小寫不敏感。 6、yiifiltersAccessRule::matchCallback:指定一個PHP回調(diào)函數(shù)用于 判定該規(guī)則是否滿足條件。(譯者注:此處的回調(diào)函數(shù)是匿名函數(shù)) 7、yiifiltersAccessRule::denyCallback: 指定一個PHP回調(diào)函數(shù), 當(dāng)這個規(guī)則不滿足條件時該函數(shù)會被調(diào)用。(譯者注:此處的回調(diào)函數(shù)是匿名函數(shù))
以下例子展示了如何使用 matchCallback 選項, 可使你設(shè)計任意的訪問權(quán)限檢查邏輯:
use yiifiltersAccessControl; 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) 提供了一個簡單而強大的集中式存取控制機制。
Yii 實現(xiàn)了通用的分層的 RBAC,遵循的模型是 NIST RBAC model. 它通過 yiirbacManagerInterface 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)限時進行的。
例如,"改帖" 的權(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)用組件 yiibaseApplication::authManager 。
Yii 提供了兩套授權(quán)管理器: yiirbacPhpManager 和 yiirbacDbManager。前者使用 PHP 腳本存放授權(quán)數(shù)據(jù), 而后者使用數(shù)據(jù)庫存放授權(quán)數(shù)據(jù)。 如果你的應(yīng)用不要求大量的動態(tài)角色和權(quán)限管理, 你可以考慮使用前者。
使用 PhpManager
以下代碼展示使用 yiirbacPhpManager 時如何在應(yīng)用配置文件中配置 authManager:
return [ // ... "components" => [ "authManager" => [ "class" => "yii bacPhpManager", ], // ... ], ];
現(xiàn)在可以通過 Yii::$app->authManager 訪問 authManager 。
yiirbacPhpManager 默認(rèn)將 RBAC 數(shù)據(jù)保存在 @app/rbac 目錄下的文件中。 如果權(quán)限層次數(shù)據(jù)在運行時會被修改,需確保WEB服務(wù)器進程對該目錄和其中的文件有寫權(quán)限。
以下代碼展示使用 yiirbacDbManager 時如何在應(yīng)用配置文件中配置 authManager:
return [
// ... "components" => [ "authManager" => [ "class" => "yii bacDbManager", ], // ... ],
];
DbManager 使用4個數(shù)據(jù)庫表存放它的數(shù)據(jù):
yiirbacDbManager::$itemTable: 該表存放授權(quán)條目(譯者注:即角色和權(quán)限)。默認(rèn)表名為 "auth_item" 。
yiirbacDbManager::$itemChildTable: 該表存放授權(quán)條目的層次關(guān)系。默認(rèn)表名為 "auth_item_child"。
yiirbacDbManager::$assignmentTable: 該表存放授權(quán)條目對用戶的指派情況。默認(rèn)表名為 "auth_assignment"。
yiirbacDbManager::$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ù):
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)用允許用戶注冊,你需要在注冊時給新用戶指派一次角色。
例如, 在高級項目模板中,要讓所有注冊用戶成為作者,你需要如下例所示修改 frontendmodelsSignupForm::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ī)則是 yiirbacRule 的派生類。 它需要實現(xiàn) yiirbacRule::execute() 方法。
在之前我們創(chuàng)建的層次結(jié)構(gòu)中,作者不能編輯自己的帖子,我們來修正這個問題。 首先我們需要一個規(guī)則來認(rèn)證當(dāng)前用戶是帖子的作者:
namespace app bac; use yii bacRule; /** * 檢查 authorID 是否和通過參數(shù)傳進來的 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 bacAuthorRule;$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);使用默認(rèn)角色
所謂默認(rèn)角色就是 隱式 地指派給 所有 用戶的角色。不需要調(diào)用 yiirbacManagerInterface::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 bac; use Yii; use yii bacRule; /** * 檢查是否匹配用戶的組 */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 bacUserGroupRule; $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 時指定 yiirbacBaseManager::$defaultRoles 選項(譯者注:在應(yīng)用配置文件中的組件部分配置):
return [ // ... "components" => [ "authManager" => [ "class" => "yii bacPhpManager", "defaultRoles" => ["admin", "author"], ], // ... ], ];
現(xiàn)在如果你執(zhí)行一個存取權(quán)限檢查, 判定該規(guī)則時, admin 和 author 兩個角色都將會檢查。如果規(guī)則返回 true ,意思是角色符合當(dāng)前用戶。基于上述規(guī)則 的實現(xiàn),意味著如果某用戶的 group 值為 1 , admin 角色將賦予該用戶, 如果 group 值是 2 則將賦予author 角色。處理密碼(Passwords)
好的安全策略對任何應(yīng)用的健康和成功極其重要。
不幸的是,許多開發(fā)者在遇到安全問題時,因為認(rèn)識不夠或者實現(xiàn)起來比較麻煩,都不是很注意細(xì)節(jié)。
為了讓你的 Yii 應(yīng)用程序盡可能的安全, Yii 囊括了一些卓越并簡單易用的安全特性。
密碼的哈希與驗證大部分開發(fā)者知道密碼不能以明文形式存儲,但是許多開發(fā)者仍認(rèn)為使用 md5 或者 sha1 來哈希化密碼是安全的。
一度,使用上述的哈希算法是足夠安全的,但是,現(xiàn)代硬件的發(fā)展使得短時間內(nèi)暴力破解上述算法生成的哈希串成為可能。
為了即使在最糟糕的情況下(你的應(yīng)用程序被破解了)也能給用戶密碼提供增強的安全性,你需要使用一個能夠?qū)贡┝ζ平夤舻墓K惴ǎ壳白詈玫倪x擇是 bcrypt。
在 PHP 中,你可以通過 crypt 函數(shù) 生成 bcrypt 哈希。Yii 提供了兩個幫助函數(shù)以讓使用crypt 來進行安全的哈希密碼生成和驗證更加容易。
當(dāng)一個用戶為第一次使用,提供了一個密碼時(比如:注冊時),密碼就需要被哈希化。
$hash = Yii::$app->getSecurity()->generatePasswordHash($password);
哈希串可以被關(guān)聯(lián)到對應(yīng)的模型屬性,這樣,它可以被存儲到數(shù)據(jù)庫中以備將來使用。
當(dāng)一個用戶嘗試登錄時,表單提交的密碼需要使用之前的存儲的哈希串來驗證:
if (Yii::$app->getSecurity()->validatePassword($password, $hash)) { // all good, logging user in } else { // wrong password }生成偽隨機數(shù)
偽隨機數(shù)據(jù)在許多場景下都非常有用。
比如當(dāng)通過郵件重置密碼時,你需要生成一個令牌,將其保存到數(shù)據(jù)庫中,并通過郵件發(fā)送到終端用戶那里以讓其證明其對某個賬號的所有權(quán)。
這個令牌的唯一性和難猜解性非常重要,否則,就存在攻擊者猜解令牌,并重置用戶的密碼的可能性。
Yii 安全助手使得生成偽隨機數(shù)據(jù)非常簡單:
$key = Yii::$app->getSecurity()->generateRandomString();
注意,你需要安裝有 openssl 擴展,以生成密碼的安全隨機數(shù)據(jù)。
加密與解密Yii 提供了方便的幫助函數(shù)來讓你用一個安全秘鑰來加密解密數(shù)據(jù)。數(shù)據(jù)通過加密函數(shù)進行傳輸,這樣只有擁有安全秘鑰的人才能解密。
比如,我們需要存儲一些信息到我們的數(shù)據(jù)庫中,但是,我們需要保證只有擁有安全秘鑰的人才能看到它(即使應(yīng)用的數(shù)據(jù)庫泄露)
// $data and $secretKey are obtained from the form $encryptedData = Yii::$app->getSecurity()->encryptByPassword($data, $secretKey); // store $encryptedData to database
隨后,當(dāng)用戶需要讀取數(shù)據(jù)時:
// $secretKey is obtained from user input, $encryptedData is from the database $data = Yii::$app->getSecurity()->decryptByPassword($encryptedData, $secretKey);校驗數(shù)據(jù)完整性
有時,你需要驗證你的數(shù)據(jù)沒有第三方篡改或者使用某種方式破壞了。Yii 通過兩個幫助函數(shù),提供了一個簡單的方式來進行數(shù)據(jù)的完整性校驗。
首先,將由安全秘鑰和數(shù)據(jù)生成的哈希串前綴到數(shù)據(jù)上。
// $secretKey our application or user secret, $genuineData obtained from a reliable source $data = Yii::$app->getSecurity()->hashData($genuineData, $secretKey);
驗證數(shù)據(jù)完整性是否被破壞了。
// $secretKey our application or user secret, $data obtained from an unreliable source $data = Yii::$app->getSecurity()->validateData($data, $secretKey);
你同樣可以給控制器或者 action 設(shè)置它的 enableCsrfValidation 屬性來多帶帶禁用 CSRF 驗證。
namespace appcontrollers; use yiiwebController; class SiteController extends Controller{ public $enableCsrfValidation = false; public function actionIndex() { // CSRF validation will not be applied to this and other actions } }
為了給某個定制的 action 關(guān)閉 CSRF 驗證,你可以:
namespace appcontrollers; use yiiwebController; class SiteController extends Controller{ public function beforeAction($action) { // ...set `$this->enableCsrfValidation` here based on some conditions... // call parent method that will check CSRF if such property is true. return parent::beforeAction($action); } }一些最佳安全實踐(Best Practices)
基本準(zhǔn)則無論是開發(fā)何種應(yīng)用程序,我們都有兩條基本的安全準(zhǔn)則:
過濾輸入
轉(zhuǎn)義輸出
過濾輸入過濾輸入的意思是,用戶輸入不應(yīng)該認(rèn)為是安全的,你需要總是驗證你獲得的輸入值是在允許范圍內(nèi)。
比如,我們假設(shè) sorting 只能指定為 title, created_at 和 status 三個值,然后,這個值是由用戶輸入提供的,那么,最好在我們接收參數(shù)的時候,檢查一下這個值是否是指定的范圍。
對于基本的 PHP 而言,上述做法類似如下:
$sortBy = $_GET["sort"]; if (!in_array($sortBy, ["title", "created_at", "status"])) { throw new Exception("Invalid sort value."); }
在 Yii 中,很大可能性,你會使用 表單校驗器 來執(zhí)行類似的檢查。
轉(zhuǎn)義輸出轉(zhuǎn)義輸出的意思是,根據(jù)我們使用數(shù)據(jù)的上下文環(huán)境,數(shù)據(jù)需要被轉(zhuǎn)義。
比如:在 HTML 上下文,你需要轉(zhuǎn)義 <,> 之類的特殊字符。在 JavaScript 或者 SQL 中,也有其他的特殊含義的字符串需要被轉(zhuǎn)義。
由于手動的給所用的輸出轉(zhuǎn)義容易出錯,Yii 提供了大量的工具來在不同的上下文執(zhí)行轉(zhuǎn)義。
避免 SQL 注入SQL 注入發(fā)生在查詢語句是由連接未轉(zhuǎn)義的字符串生成的場景,比如:
$username = $_GET["username"]; $sql = "SELECT * FROM user WHERE username = "$username"";
除了提供正確的用戶名外,攻擊者可以給你的應(yīng)用程序輸入類似 "; DROP TABLE user; --` 的語句。 這將會導(dǎo)致生成如下的 SQL :
SELECT * FROM user WHERE username = ""; DROP TABLE user; --"
這是一個合法的查詢語句,并將會執(zhí)行以空的用戶名搜索用戶操作,然后,刪除 user 表。這極有可能導(dǎo)致網(wǎng)站出差,數(shù)據(jù)丟失。(你是否進行了規(guī)律的數(shù)據(jù)備份?)
在 Yii 中,大部分的數(shù)據(jù)查詢是通過 Active Record 進行的,而其是完全使用 PDO 預(yù)處理語句執(zhí)行 SQL 查詢的。在預(yù)處理語句中,上述示例中,構(gòu)造 SQL 查詢的場景是不可能發(fā)生的。
有時,你仍需要使用 raw queries 或者 query builder。在這種情況下,你應(yīng)該使用安全的方式傳遞參數(shù)。如果數(shù)據(jù)是提供給表列的值,最好使用預(yù)處理語句:
// query builder $userIDs = (new Query()) ->select("id") ->from("user") ->where("status=:status", [":status" => $status]) ->all(); // DAO $userIDs = $connection ->createCommand("SELECT id FROM user where status=:status") ->bindValues([":status" => $status]) ->queryColumn();
如果數(shù)據(jù)是用于指定列的名字,或者表的名字,最好的方式是只允許預(yù)定義的枚舉值。
function actionList($orderBy = null){ if (!in_array($orderBy, ["name", "status"])) { throw new BadRequestHttpException("Only name and status are allowed to order by.") } // ... }
如果上述方法不行,表名或者列名應(yīng)該被轉(zhuǎn)義。 Yii 針對這種轉(zhuǎn)義提供了一個特殊的語法,這樣可以在所有支持的數(shù)據(jù)庫都使用一套方案。
$sql = "SELECT COUNT($column) FROM {{table}}"; $rowCount = $connection->createCommand($sql)->queryScalar();防止 XSS 攻擊
XSS 或者跨站腳本發(fā)生在輸出 HTML 到瀏覽器時,輸出內(nèi)容沒有正確的轉(zhuǎn)義。
例如,如果用戶可以輸入其名稱,那么他輸入 而非其名字 Alexander,所有輸出沒有轉(zhuǎn)義直接輸出用戶名的頁面都會執(zhí)行 JavaScript 代碼 alert("Hello!");,這會導(dǎo)致瀏覽器頁面上出現(xiàn)一個警告彈出框。
就具體的站點而言,除了這種無意義的警告輸出外,這樣的腳本可以以你的名義發(fā)送一些消息到后臺,甚至執(zhí)行一些銀行交易行為。
避免 XSS 攻擊在 Yii 中非常簡單,有如下兩種一般情況:
你希望數(shù)據(jù)以純文本輸出。
你希望數(shù)據(jù)以 HTML 形式輸出。
如果你需要的是純文本,你可以如下簡單的轉(zhuǎn)義:
= yiihelpersHtml::encode($username) ?>
如果是 HTML ,我們可以用 HtmlPurifier 幫助類來執(zhí)行:
= yiihelpersHtmlPurifier::process($description) ?>
注意: HtmlPurifier 幫助類的處理過程較為費時,建議增加緩存:
防止 CSRF 攻擊
CSRF 是跨站請求偽造的縮寫。這個攻擊思想源自許多應(yīng)用程序假設(shè)來自用戶的瀏覽器請求是由用戶自己產(chǎn)生的,而事實并非如此。
比如說:an.example.com 站點有一個 /logout URL,當(dāng)以 GET 請求訪問時,登出用戶。如果它是由用戶自己操作的,那么一切都沒有問題。但是,有一天壞人在一個用戶經(jīng)常訪問的論壇發(fā)了一個
內(nèi)容的帖子。瀏覽器無法辨別請求一個圖片還是一個頁面,所以,當(dāng)用戶打開含有上述標(biāo)簽的頁面時,他將會從 an.example.com 登出。
上面就是最原始的思想。有人可能會說,登出用戶也不是什么嚴(yán)重問題,然而,我們發(fā)送一些 POST 數(shù)據(jù)其實也不是很麻煩的事情。
為了避免 CSRF 攻擊,你總是需要:
遵循 HTTP 準(zhǔn)則,比如 GET 不應(yīng)該改變應(yīng)用的狀態(tài)。
保證 Yii CSRF 保護開啟。
防止文件暴露默認(rèn)的服務(wù)器 webroot 目錄指向包含有 index.php 的 web 目錄。
在共享托管環(huán)境下,這樣是不可能的,這樣導(dǎo)致了所有的代碼,配置,日志都在webroot目錄。
如果是這樣,別忘了拒絕除了 web 目錄以外的目錄的訪問權(quán)限。如果沒法這樣做,考慮將你的應(yīng)用程序托管在其他地方。
在生產(chǎn)環(huán)境關(guān)閉調(diào)試信息和工具在調(diào)試模式下, Yii 展示了大量的錯誤信息,這樣是對開發(fā)有用的。同樣,這些調(diào)試信息對于攻擊者而言也是方便其用于破解數(shù)據(jù)結(jié)構(gòu),配置值,以及你的部分代碼。
永遠不要在生產(chǎn)模式下將你的 index.php 中的 YII_DEBUG 設(shè)置為 true。
你同樣也不應(yīng)該在生產(chǎn)模式下開啟 Gii。
它可以被用于獲取數(shù)據(jù)結(jié)構(gòu)信息,代碼,以及簡單的用 Gii 生成的代碼覆蓋你的代碼。
調(diào)試工具欄同樣也應(yīng)該避免在生產(chǎn)環(huán)境出現(xiàn),除非非常有必要。它將會暴露所有的應(yīng)用和配置的詳情信息。如果你確定需要,反復(fù)確認(rèn)其訪問權(quán)限限定在你自己的 IP。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/21642.html
摘要:簡述是一個強大的代碼生成器,主要用于后臺代碼生成。下面列出由生成的文件,以便你研習(xí)功能和實現(xiàn),或修改它們控制器模型和視圖補充被設(shè)計成高度可定制和可擴展的代碼生成工具。使用生成代碼是一個基于界面的代碼生成工具。 簡述 Gii 是一個強大的代碼生成器,主要用于后臺代碼生成。 開始 Gii Gii 是 Yii 中的一個模塊。可以通過配置應(yīng)用的 yiibaseApplication::modu...
摘要:它由一個或多個類組成,它們在控制臺環(huán)境下通常被稱為命令。控制臺入口腳本通常被稱為,位于應(yīng)用程序的根目錄。選項通過覆蓋在中的方法,你可以指定可用于控制臺命令選項。參數(shù)將傳遞給請求的子命令對應(yīng)的操作方法。通常,執(zhí)行成功的命令會返回。 簡述 控制臺應(yīng)用程序的結(jié)構(gòu)非常類似于 Yii 的一個 Web 應(yīng)用程序,主要用于終端服務(wù)器執(zhí)行。 控制臺命令 控制臺應(yīng)用程序的結(jié)構(gòu)非常類似于 Yii 的一個 ...
摘要:把所有的增量數(shù)據(jù)庫遷移提交到生產(chǎn)環(huán)境數(shù)據(jù)庫當(dāng)中。如果其中任意一個遷移提交失敗了,那么這條命令將會退出并停止剩下的那些還未執(zhí)行的遷移。執(zhí)行這條命令期間不會有任何的遷移會被提交或還原。 簡述 數(shù)據(jù)遷移就是數(shù)據(jù)庫表在團隊建的遷移操作,達到團隊相互間的信息同步,數(shù)據(jù)統(tǒng)一。 數(shù)據(jù)庫遷移 一般步驟: 1、在 yii2 的 migrate 中,通常用來對數(shù)據(jù)庫數(shù)據(jù)表進行修改操作,主要對結(jié)構(gòu)和小部分?jǐn)?shù)...
摘要:簡述這里簡單歸納總結(jié)關(guān)于的錯誤處理和日志記錄的操作。錯誤處理器會正確地設(shè)置響應(yīng)的狀態(tài)碼并使用合適的錯誤視圖頁面來顯示錯誤信息。記錄一個警告消息用來指示一些已經(jīng)發(fā)生的意外。的義務(wù)是正確處理日志消息。相應(yīng)的消息通過被記錄。 簡述 這里簡單歸納總結(jié)關(guān)于Yii的錯誤處理和日志記錄的操作。 錯誤處理(Errors) Yii 內(nèi)置了一個yiiwebErrorHandler錯誤處理器,它使錯誤處理更...
摘要:簡述模塊是中的架構(gòu)的板塊,主要負(fù)責(zé)數(shù)據(jù)的展示,渲染模板文件,展示數(shù)據(jù)內(nèi)容。此外在一個視圖中還可以引入多個視圖文件,也是通過方法實現(xiàn)。布局文件的數(shù)據(jù)默認(rèn)以顯示,也可以用數(shù)據(jù)塊的形式渲染到視圖上。必須要確認(rèn)生成一次,才會正式生成新首頁。 簡述 View模塊是Yii中的MVC架構(gòu)的V板塊,主要負(fù)責(zé)數(shù)據(jù)的展示,渲染模板文件,展示數(shù)據(jù)內(nèi)容。 基本概念 MVC在Yii里面有一個Views文件夾,里...
閱讀 1355·2021-11-15 11:45
閱讀 3123·2021-09-27 13:36
閱讀 2867·2019-08-30 15:54
閱讀 984·2019-08-29 12:38
閱讀 2905·2019-08-29 11:22
閱讀 2983·2019-08-26 13:52
閱讀 2025·2019-08-26 13:30
閱讀 584·2019-08-26 10:37