對於需要限制某些使用者訪問的網頁,我們需要使用驗證(Authentication)和授權(Authorization)。 驗證是指核查一個人是否真的是他自己所聲稱的那個人。這通常需要一個使用者名稱和密碼, 但也包括任何其他可以表明身份的方式,例如一個智慧卡,指紋等等。 授權則是找出已通過驗證的使用者是否允許操作特定的資源。 這一般是通過查詢此使用者是否屬於一個有權訪問該資源的角色來判斷的。
Yii 有一個內建的驗證/授權(auth)架構,用起來很方便,還能對其進行自訂,使其符合特殊的需求。
Yii auth 架構的核心是一個預定義的 使用者(user)應用組件 它是一個實現了 IWebUser 介面的對象。 此使用者組件代表目前使用者的持久性認證資訊。我們可以通過Yii::app()->user在任何地方訪問它。
使用此使用者組件,我們可以通過 CWebUser::isGuest 檢查檢查一個使用者是否登陸; 可以 登入(login) 或 登出(logout) 一個使用者;我們可以通過CWebUser::checkAccess檢查此使用者是否可以執行特定的操作;還可以擷取此使用者的唯一標識(unique identifier)及其他持久性身份資訊。
1. 定義身份類 (Defining Identity Class)
為了驗證一個使用者,我們定義一個有驗證邏輯的身份類。這個身份類實現IUserIdentity 介面。
不同的類可能實現不同的驗證方式(例如:OpenID,LDAP)。最好是繼承 CUserIdentity,此類是居於使用者名稱和密碼的驗證方式。
定義身份類的主要工作是實現IUserIdentity::authenticate方法。在使用者會話中根據需要,身份類可能需要定義別的身份資訊
應用執行個體
下面的例子,我們使用Active Record來驗證提供的使用者名稱、密碼和資料庫的使用者表是否吻合。我們通過重寫getId函數來返回驗證過程中獲得的_id變數(預設的實現則是返回使用者名稱)。在驗證過程中,我們還藉助CBaseUserIdentity::setState函數把獲得的title資訊存成一個狀態。
-
The implementation of the authenticate() to use the database to validate credentials.
-
Overriding the CUserIdentity::getId() method to return the _id property because the default implementation returns the username as the ID.
-
Using the setState() (CBaseUserIdentity::setState) method to demonstrate storing other information that can easily be retrieved upon subsequent requests.
class UserIdentity extends CUserIdentity{ private $_id; public function authenticate() { $record=User::model()->findByAttributes(array('username'=>$this->username)); if($record===null) $this->errorCode=self::ERROR_USERNAME_INVALID; else if($record->password!==md5($this->password)) $this->errorCode=self::ERROR_PASSWORD_INVALID; else { $this->_id=$record->id; $this->setState('title', $record->title); $this->errorCode=self::ERROR_NONE; } return !$this->errorCode; } public function getId() { return $this->_id; }}
作為狀態儲存的資訊(通過調用CBaseUserIdentity::setState)將被傳遞給CWebUser。而後者則把這些資訊存放在一個永久儲存媒介上(如session)。我們可以把這些資訊當作CWebUser的屬性來使用。例如,為了獲得目前使用者的title資訊,我們可以使用Yii::app()->user->title(這項功能是在1.0.3版本引入的。在之前的版本裡,我們需要使用Yii::app()->user->getState('title'))。
提示: 預設情況下,CWebUser用session來儲存使用者身份資訊。如果允許基於cookie方式登入(通過設定 CWebUser::allowAutoLogin為 true),使用者身份資訊將被存放在cookie中。確記敏感資訊不要存放(例如 password) 。
2. 登入和登出(Login and Logout)
使用身份類和使用者組件,我們方便的實現登入和登出。
// 使用提供的使用者名稱和密碼登入使用者$identity=new UserIdentity($username,$password);if($identity->authenticate()) Yii::app()->user->login($identity);else echo $identity->errorMessage;......// 登出目前使用者Yii::app()->user->logout();
Here we are creating a new UserIdentity object and passing in the authentication credentials (i.e. the$username and $password values submitted by the user) to its constructor. We then simply call theauthenticate() method. If successful, we pass the identity information into the CWebUser::login method, which will store the identity information into persistent storage (PHP session by default) for retrieval upon subsequent requests. If the authentication fails, we can interrogate the errorMessage property for more information as to why it failed.
Whether or not a user has been authenticated can easily be checked throughout the application by usingYii::app()->user->isGuest. If using persistent storage like session (the default) and/or a cookie (discussed below) to store the identity information, the user can remain logged in upon subsequent requests. In this case, we don't need to use the UserIdentity class and the entire login process upon each request. Rather CWebUser will automatically take care of loading the identity information from this persistent storage and will use it to determine whether Yii::app()->user->isGuest returns true or false.
基於Cookie 的登入
預設情況下,使用者將根據session configuration完成一序列inactivity動作後登出。設定使用者組件的allowAutoLogin屬性為true和在CWebUser::login方法中設定一個期間參數來改變這個行為。即使使用者關閉瀏覽器,此使用者將保留使用者登陸狀態時間為被設定的期間之久。前提是使用者的瀏覽器接受cookies。
// 保留使用者登陸狀態時間7天// 確保使用者組件的allowAutoLogin被設定為true。Yii::app()->user->login($identity,3600*24*7);
As we mentioned above, when cookie-based login is enabled, the states stored viaCBaseUserIdentity::setState will be saved in the cookie as well. The next time when the user is logged in, these states will be read from the cookie and made accessible via Yii::app()->user.
Although Yii has measures to prevent the state cookie from being tampered on the client side, we strongly suggest that security sensitive information be not stored as states. Instead, these information should be restored on the server side by reading from some persistent storage on the server side (e.g. database).
In addition, for any serious Web applications, we recommend using the following strategy to enhance the security of cookie-based login.
-
When a user successfully logs in by filling out a login form, we generate and store a random key in both the cookie state and in persistent storage on server side (e.g. database).
-
Upon a subsequent request, when the user authentication is being done via the cookie information, we compare the two copies of this random key and ensure a match before logging in the user.
-
If the user logs in via the login form again, the key needs to be re-generated.
By using the above strategy, we eliminate the possibility that a user may re-use an old state cookie which may contain outdated state information.
To implement the above strategy, we need to override the following two methods:
-
CUserIdentity::authenticate(): this is where the real authentication is performed. If the user is authenticated, we should re-generate a new random key, and store it in the database as well as in the identity states via CBaseUserIdentity::setState.
-
CWebUser::beforeLogin(): this is called when a user is being logged in. We should check if the key obtained from the state cookie is the same as the one from the database.
3. 存取控制過濾器(Access Control Filter)
存取控制過濾器是檢查目前使用者是否能執行訪問的controller action的初步授權模式。這種授權模式基於使用者名稱,客戶IP地址和訪問類型,以命名為 "accessControl"的過濾器的方式提供.
小貼士: 存取控制過濾器適用於簡單的驗證。需要複雜的存取控制,需要使用將要講解到的基於角色存取控制(role-based access (RBAC)).
在控制器(controller)裡重載CController::filters方法設定訪問過濾器來控制訪問動作(看 Filter 瞭解更多過濾器設定資訊)。
class PostController extends CController{ ...... public function filters() { return array( 'accessControl', ); }}
在上面,設定的access control過濾器將應用於PostController裡每個動作。過濾器具體的授權規則通過重載控制器的CController::accessRules方法來指定。
class PostController extends CController{ ...... public function accessRules() { return array( array('deny', 'actions'=>array('create', 'edit'), 'users'=>array('?'), ), array('allow', 'actions'=>array('delete'), 'roles'=>array('admin'), ), array('deny', 'actions'=>array('delete'), 'users'=>array('*'), ), ); }}
上面設定了三個規則,每個用個數組表示。數組的第一個元素不是'allow'就是'deny',其他的是名-值成對形式設定規則參數的。上面的規則這樣理解:create和edit動作不能被匿名執行;delete動作可以被admin角色的使用者執行;delete動作不能被任何人執行。
訪問規則是一個一個按照設定的順序一個一個來執行判斷的。和當前判斷模式(例如:使用者名稱、角色、用戶端IP、地址)相匹配的第一條規則決定授權的結果。如果這個規則是allow,則動作可執行;如果是deny,不能執行;如果沒有規則匹配,動作可以執行。
info|提示:為了確保某類動作在沒允許情況下不被執行,設定一個匹配所有人的deny規則在最後,類似如下:
return array( // ... 別的規則... // 以下匹配所有人規則拒絕'delete'動作 array('deny', 'action'=>'delete', ),);
因為如果沒有設定規則匹配動作,動作預設會被執行。
訪問規則通過如下的上下文參數設定:
-
actions: 設定哪個動作匹配此規則。
-
users: 設定哪個使用者匹配此規則。 此目前使用者的name 被用來匹配. 三種設定字元在這裡可以用:
-
*: 任何使用者,包括匿名和驗證通過的使用者。
-
?: 匿名使用者。
-
@: 驗證通過的使用者。
-
roles: 設定哪個角色匹配此規則。 這裡用到了將在後面描述的role-based access control技術。In particular, the rule is applied if CWebUser::checkAccess returns true for one of the roles.提示,使用者角色應該被設定成allow規則,因為角色代表能做某些事情。
-
ips: 設定哪個用戶端IP匹配此規則。
-
verbs: 設定哪種請求類型(例如:GET, POST)匹配此規則。
-
expression: 設定一個PHP運算式。它的值用來表明這條規則是否適用。在運算式,你可以使用一個叫$user的變數,它代表的是Yii::app()->user。這個選項是在1.0.3版本裡引入的。
授權處理結果(Handling Authorization Result)
當授權失敗,即,使用者不允許執行此動作,以下的兩種可能將會產生:
當配置loginUrl 屬性,可以用相對和絕對URL。還可以使用數組通過CWebApplication::createUrl來產生URL。第一個元素將設定route 為登入控制器動作,其他為名-值成對形式的GET參數。如下,
array( ...... 'components'=>array( 'user'=>array( // 這實際上是預設值 'loginUrl'=>array('site/login'), ), ),)
如果瀏覽器重定位到登入頁面,而且登入成功,我們將重定位瀏覽器到引起驗證失敗的頁面。我們怎麼知道這個值呢?我們可以通過使用者組件的returnUrl 屬性獲得。我們因此可以用如下執行重新導向:
Yii::app()->request->redirect(Yii::app()->user->returnUrl);
4. 角色型存取控制(Role-Based Access Control)
角色型存取控制提供了一種簡單而又強大的集中存取控制。 請參閱維基文章瞭解更多詳細的RBAC與其他較傳統的存取控制模式的比較。
Yii 通過其 authManager 組件實現了分等級的 RBAC 結構。 在下文中,我們將首先介紹在此結構中用到的主要概念。然後講解怎樣定義用於授權的資料。在最後,我們看看如何利用這些授權資料執行訪問檢查。
概覽(Overview)
在 Yii 的 RBAC 中,一個基本的概念是 授權項目(authorization item)。 一個授權項目就是一個做某件事的許可(例如新帖發布,使用者管理)。根據其粒度和目標受眾, 授權項目可分為 操作(operations),任務(tasks) 和角色(roles)。 一個角色由若干工作群組成,一個任務由若干操作組成, 而一個操作就是一個許可,不可再分。 例如,我們有一個系統,它有一個 管理員 角色,它由 文章管理 和 使用者管理 工作群組成。 使用者管理 任務可以包含建立使用者,修改使用者 和 刪除使用者 操作組成。 為保持靈活性,Yii 還允許一個角色包含其他角色或操作,一個任務可以包含其他動作,一個操作可以包括其他動作。
授權項目是通過它的名字唯一識別的。
一個授權項目可能與一個 商務規則 關聯。 商務規則是一段 PHP 代碼,在進行涉及授權項目的訪問檢查時將會被執行。 僅在執行返回 true 時,使用者才會被視為擁有此授權項目所代表的許可權許可。 例如,當定義一個updatePost(更新文章) 操作時,我們可以添加一個檢查目前使用者 ID 是否與此文章的作者 ID 相同的商務規則, 這樣,只有作者自己才有更新文章的許可權。
通過授權項目,我們可以構建一個 授權等級體系 。在等級體系中,如果項目 A 由另外的項目 B 組成(或者說 A 繼承了 B 所代表的許可權),則 A 就是 B 的父項目。 一個授權項目可以有多個子項目,也可以有多個父項目。因此,授權等級體系是一個偏序圖(partial-order graph)結構而不是一種樹狀結構。 在這種等級體系中,角色項目位於最頂層,操作項目位於最底層,而任務項目位於兩者之間。
一旦有了授權等級體系,我們就可以將此體系中的角色指派給使用者。 而一個使用者一旦被賦予一個角色,他就會擁有此角色所代表的許可權。 例如,如果我們賦予一個使用者 管理員 的角色,他就會擁有管理員的許可權,包括 文章管理 和 使用者管理 (以及相應的操作,例如 建立使用者)。
現在有趣的部分開始了,在一個控制器動作中,我們想檢查目前使用者是否可以刪除指定的文章。 利用 RBAC 等級體系和分配,可以很容易做到這一點。如下:
if(Yii::app()->user->checkAccess('deletePost')){ // 刪除此帖}
5. 配置授權管理員(Authorization Manager)
在我們準備定義一個授權等級體系並執行存取權限檢查之前, 我們需要配置一下 authManager 應用組件。 Yii 提供了兩種授權管理員: CPhpAuthManager 和 CDbAuthManager。前者將授權資料存放區在一個 PHP 指令檔中而後者儲存在資料庫中。 配置 authManager 應用組件時,我們需要指定使用哪個授權管理員組件類, 以及所選授權管理員組件的初始化屬性值。例如:
return array( 'components'=>array( 'db'=>array( 'class'=>'CDbConnection', 'connectionString'=>'sqlite:path/to/file.db', ), 'authManager'=>array( 'class'=>'CDbAuthManager', 'connectionID'=>'db', ), ),);
然後,我們便可以使用 Yii::app()->authManager 訪問 authManager 應用組件。
6. 定義授權等級體系
定義授權等級體總共分三步:定義授權項目,建立授權項目之間的關係,還要分配角色給使用者。 authManager 應用組件提供了用於完成這三項任務的一系列 API 。
要定義一個授權項目,可調用下列方法之一,具體取決於項目的類型:
建立授權項目之後,我們就可以調用下列方法建立授權項目之間的關係:
最後,我們調用下列方法將角色指派給使用者。
-
CAuthManager::assign
-
CAuthManager::revoke
下面的代碼示範了使用 Yii 提供的 API 構建一個授權體系的例子:
$auth=Yii::app()->authManager;$auth->createOperation('createPost','create a post');$auth->createOperation('readPost','read a post');$auth->createOperation('updatePost','update a post');$auth->createOperation('deletePost','delete a post');$bizRule='return Yii::app()->user->id==$params["post"]->authID;';$task=$auth->createTask('updateOwnPost','update a post by author himself',$bizRule);$task->addChild('updatePost');$role=$auth->createRole('reader');$role->addChild('readPost');$role=$auth->createRole('author');$role->addChild('reader');$role->addChild('createPost');$role->addChild('updateOwnPost');$role=$auth->createRole('editor');$role->addChild('reader');$role->addChild('updatePost');$role=$auth->createRole('admin');$role->addChild('editor');$role->addChild('author');$role->addChild('deletePost');$auth->assign('reader','readerA');$auth->assign('author','authorB');$auth->assign('editor','editorC');$auth->assign('admin','adminD');$auth->save();
特別提醒:如果沒有在UserIdentity中重寫getId方法,則上述assign沒有問題,如果重寫了getId返回使用者的Id,那麼相應的應該在assign語句中將相應的使用者名稱修改成對應的使用者Id,否則在使用checkAccess方法的時候無效!
建立此授權等級體系後,authManager 組件(例如 CPhpAuthManager, CDbAuthManager) 就會自動載入授權項目。因此,我們只需要運行上述代碼一次,並不需要在每個請求中都要運行。
資訊: 上面的樣本看起來比較冗長拖遝,它主要用於示範的目的。 開發人員通常需要開發一些用於管理的使用者介面,這樣終端使用者可以通過介面更直觀地建立一個授權等級體系。
7. 使用商務規則
在定義授權等級體系時,我們可以將 商務規則 關聯到一個角色,一個任務,或者一個操作。 我們也可以在為一個使用者指派角色時關聯一個商務規則。 一個商務規則就是一段 PHP 代碼,在我們執行許可權檢查時被執行。 代碼返回的值用來決定是否將角色或分配應用到目前使用者。 在上面的例子中,我們把一條商務規則關聯到了updateOwnPost 任務。 在商務規則中,我們簡單的檢查了目前使用者的 ID 是否與指定文章的作者 ID 相同。$params 數組中的文章(post)資訊由開發人員在執行許可權檢查時提供。
許可權檢查
要執行許可權檢查,我們首先需要知道授權項目的名字。 例如,要檢查目前使用者是否可以建立文章,我們需要檢查他是否擁有 createPost 所表示的許可權。 然後我們調用 CWebUser::checkAccess 執行許可權檢查:
if(Yii::app()->user->checkAccess('createPost')){ // 建立文章}
如果授權規則關聯了一條需要額外參數的商務規則,我們也可以傳遞給它。例如,要檢查一個使用者是否可以更新文章, 我們可以通過 $params 傳遞文章的資料:
$params=array('post'=>$post);if(Yii::app()->user->checkAccess('updateOwnPost',$params)){ // 更新文章}
使用預設角色
注意: 預設角色功能從 1.0.3 版本起可用。
許多 Web 程式需要一些可以分配給系統中所有或大多數使用者的比較特殊的角色。 例如,我們可能想要分配一些許可權給所有已通過身分識別驗證的使用者。如果我們特意指定並儲存這些角色指派,就會引起很多維護上的麻煩。 我們可以利用 預設角色 解決這個問題。
預設角色就是一個隱式分配給每個使用者的角色,這些使用者包括通過身分識別驗證的使用者和遊客。 我們不需要顯式地將其分配給一個使用者。 當 CWebUser::checkAccess 被調用時,將會首先檢查預設的角色,就像它已經被分配給這個使用者一樣。
預設角色必須定義在 CAuthManager::defaultRoles 屬性中。 例如,下面的配置聲明了兩個角色為預設角色:authenticated 和 guest。
return array( 'components'=>array( 'authManager'=>array( 'class'=>'CDbAuthManager', 'defaultRoles'=>array('authenticated', 'guest'), ), ),);
由於預設角色會被分配給每個使用者,它通常需要關聯一個商務規則以確定角色是否真的要應用到使用者。 例如,下面的代碼定義了兩個角色, authenticated 和 guest,很高效地分別應用到了已通過身分識別驗證的使用者和遊客使用者。
$bizRule='return !Yii::app()->user->isGuest;';$auth->createRole('authenticated', 'authenticated user', $bizRule);$bizRule='return Yii::app()->user->isGuest;';$auth->createRole('guest', 'guest user', $bizRule);
以上就是Yii架構官方指南系列42——專題:驗證和授權的內容,更多相關內容請關注topic.alibabacloud.com(www.php.cn)!