mirror of
https://github.com/yiisoft/yii.git
synced 2026-03-13 03:26:52 +01:00
555 lines
20 KiB
Plaintext
555 lines
20 KiB
Plaintext
Authentication and Authorization
|
|
================================
|
|
|
|
Authentication and authorization are required for a Web page that should
|
|
be limited to certain users. Authentication is about verifying whether
|
|
someone is who he claims he is. It usually involves a username and a
|
|
password, but may include any other methods of demonstrating identity, such
|
|
as a smart card, fingerprints, etc. Authorization is finding out if the
|
|
person, once identified (authenticated), is permitted to manipulate
|
|
specific resources. This is usually determined by finding out if that
|
|
person is of a particular role that has access to the resources.
|
|
|
|
Yii has a built-in authentication/authorization (auth) framework which is
|
|
easy to use and can be customized for special needs.
|
|
|
|
The central piece in the Yii auth framework is a pre-declared *user
|
|
application component* which is an object implementing the [IWebUser]
|
|
interface. The user component represents the persistent identity
|
|
information for the current user. We can access it at any place using
|
|
`Yii::app()->user`.
|
|
|
|
Using the user component, we can check if a user is logged in or not via
|
|
[CWebUser::isGuest]; we can [login|CWebUser::login] and
|
|
[logout|CWebUser::logout] a user; we can check if the user can perform
|
|
specific operations by calling [CWebUser::checkAccess]; and we can also
|
|
obtain the [unique identifier|CWebUser::name] and other persistent identity
|
|
information about the user.
|
|
|
|
Defining Identity Class
|
|
-----------------------
|
|
|
|
In order to authenticate a user, we define an identity class which
|
|
contains the actual authentication logic. The identity class should
|
|
implement the [IUserIdentity] interface. Different classes may be
|
|
implemented for different authentication approaches (e.g. OpenID, LDAP). A
|
|
good start is by extending [CUserIdentity] which is a base class for the
|
|
authentication approach based on username and password.
|
|
|
|
The main work in defining an identity class is the implementation of the
|
|
[IUserIdentity::authenticate] method. An identity class may also declare
|
|
additional identity information that needs to be persistent during the user
|
|
session.
|
|
|
|
In the following example, we validate the given username and password
|
|
against the user table in a database using [Active
|
|
Record](/doc/guide/database.ar). We also override the `getId` method to
|
|
return the `_id` variable which is set during authentication (the default
|
|
implementation would return the username as the ID). During authentication,
|
|
we store the retrieved `title` information in a state with the same name by
|
|
calling [CBaseUserIdentity::setState].
|
|
|
|
~~~
|
|
[php]
|
|
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;
|
|
}
|
|
}
|
|
~~~
|
|
|
|
Information stored in a state (by calling [CBaseUserIdentity::setState]) will be passed
|
|
to [CWebUser] which stores them in a persistent storage, such as session.
|
|
These information can be accessed like properties of [CWebUser]. For example, we
|
|
can obtain the `title` information of the current user by `Yii::app()->user->title`
|
|
(This has been available since version 1.0.3. In prior versions, we should use
|
|
`Yii::app()->user->getState('title')`, instead.)
|
|
|
|
> Info: By default, [CWebUser] uses session as persistent storage for user
|
|
identity information. If cookie-based login is enabled (by setting
|
|
[CWebUser::allowAutoLogin] to be true), the user identity information may
|
|
also be saved in cookie. Make sure you do not declare sensitive information
|
|
(e.g. password) to be persistent.
|
|
|
|
Login and Logout
|
|
----------------
|
|
|
|
Using the identity class and the user component, we can
|
|
implement login and logout actions easily.
|
|
|
|
~~~
|
|
[php]
|
|
// Login a user with the provided username and password.
|
|
$identity=new UserIdentity($username,$password);
|
|
if($identity->authenticate())
|
|
Yii::app()->user->login($identity);
|
|
else
|
|
echo $identity->errorMessage;
|
|
......
|
|
// Logout the current user
|
|
Yii::app()->user->logout();
|
|
~~~
|
|
|
|
By default, a user will be logged out after a certain period of inactivity,
|
|
depending on the [session configuration](http://www.php.net/manual/en/session.configuration.php).
|
|
To change this behavior, we can set the [allowAutoLogin|CWebUser::allowAutoLogin]
|
|
property of the user component to be true and pass a duration parameter to
|
|
the [CWebUser::login] method. The user will then remain logged in for
|
|
the specified duration, even if he closes his browser window. Note that
|
|
this feature requires the user's browser to accept cookies.
|
|
|
|
~~~
|
|
[php]
|
|
// Keep the user logged in for 7 days.
|
|
// Make sure allowAutoLogin is set true for the user component.
|
|
Yii::app()->user->login($identity,3600*24*7);
|
|
~~~
|
|
|
|
Access Control Filter
|
|
---------------------
|
|
|
|
Access control filter is a preliminary authorization scheme that checks if
|
|
the current user can perform the requested controller action. The
|
|
authorization is based on user's name, client IP address and request types.
|
|
It is provided as a filter named as
|
|
["accessControl"|CController::filterAccessControl].
|
|
|
|
> Tip: Access control filter is sufficient for simple scenarios. For
|
|
complex access control, you may use role-based access (RBAC) which is to be
|
|
covered shortly.
|
|
|
|
To control the access to actions in a controller, we install the access
|
|
control filter by overriding [CController::filters] (see
|
|
[Filter](/doc/guide/basics.controller#filter) for more details about
|
|
installing filters).
|
|
|
|
~~~
|
|
[php]
|
|
class PostController extends CController
|
|
{
|
|
......
|
|
public function filters()
|
|
{
|
|
return array(
|
|
'accessControl',
|
|
);
|
|
}
|
|
}
|
|
~~~
|
|
|
|
In the above, we specify that the [access
|
|
control|CController::filterAccessControl] filter should be applied to every
|
|
action of `PostController`. The detailed authorization rules used by the
|
|
filter are specified by overriding [CController::accessRules] in the
|
|
controller class.
|
|
|
|
~~~
|
|
[php]
|
|
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('*'),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
~~~
|
|
|
|
The above code specifies three rules, each represented as an array. The
|
|
first element of the array is either `'allow'` or `'deny'` and the rest
|
|
name-value pairs specify the pattern parameters of the rule. These rules
|
|
read: the `create` and `edit` actions cannot be executed by anonymous
|
|
users; the `delete` action can be executed by users with `admin` role;
|
|
and the `delete` action cannot be executed by anyone.
|
|
|
|
The access rules are evaluated one by one in the order they are specified.
|
|
The first rule that matches the current pattern (e.g. username, roles,
|
|
client IP, address) determines the authorization result. If this rule is an `allow`
|
|
rule, the action can be executed; if it is a `deny` rule, the action cannot
|
|
be executed; if none of the rules matches the context, the action can still
|
|
be executed.
|
|
|
|
> Tip: To ensure an action does not get executed under certain contexts,
|
|
> it is beneficial to always specify a matching-all `deny` rule at the end
|
|
> of rule set, like the following:
|
|
> ~~~
|
|
> [php]
|
|
> return array(
|
|
> // ... other rules...
|
|
> // the following rule denies 'delete' action for all contexts
|
|
> array('deny',
|
|
> 'action'=>array('delete'),
|
|
> ),
|
|
> );
|
|
> ~~~
|
|
> The reason for this rule is because if none of the rules matches a context,
|
|
> an action will be executed.
|
|
|
|
An access rule can match the following context parameters:
|
|
|
|
- [actions|CAccessRule::actions]: specifies which actions this rule
|
|
matches. This should be an array of action IDs. The comparison is case-insensitive.
|
|
|
|
- [controllers|CAccessRule::controllers]: specifies which controllers this rule
|
|
matches. This should be an array of controller IDs. The comparison is case-insensitive.
|
|
This option has been available since version 1.0.4.
|
|
|
|
- [users|CAccessRule::users]: specifies which users this rule matches.
|
|
The current user's [name|CWebUser::name] is used for matching. The comparison
|
|
is case-insensitive. Three special characters can be used here:
|
|
|
|
- `*`: any user, including both anonymous and authenticated users.
|
|
- `?`: anonymous users.
|
|
- `@`: authenticated users.
|
|
|
|
- [roles|CAccessRule::roles]: specifies which roles that this rule matches.
|
|
This makes use of the [role-based access control](#role-based-access-control)
|
|
feature to be described in the next subsection. In particular, the rule
|
|
is applied if [CWebUser::checkAccess] returns true for one of the roles.
|
|
Note, you should mainly use roles in an `allow` rule because by definition,
|
|
a role represents a permission to do something. Also note, although we use the
|
|
term `roles` here, its value can actually be any auth item, including roles,
|
|
tasks and operations.
|
|
|
|
- [ips|CAccessRule::ips]: specifies which client IP addresses this rule
|
|
matches.
|
|
|
|
- [verbs|CAccessRule::verbs]: specifies which request types (e.g.
|
|
`GET`, `POST`) this rule matches. The comparison is case-insensitive.
|
|
|
|
- [expression|CAccessRule::expression]: specifies a PHP expression whose value
|
|
indicates whether this rule matches. In the expression, you can use variable `$user`
|
|
which refers to `Yii::app()->user`. This option has been available since version 1.0.3.
|
|
|
|
|
|
### Handling Authorization Result
|
|
|
|
When authorization fails, i.e., the user is not allowed to perform the
|
|
specified action, one of the following two scenarios may happen:
|
|
|
|
- If the user is not logged in and if the [loginUrl|CWebUser::loginUrl]
|
|
property of the user component is configured to be the URL of the login
|
|
page, the browser will be redirected to that page. Note that by default,
|
|
[loginUrl|CWebUser::loginUrl] points to the `site/login` page.
|
|
|
|
- Otherwise an HTTP exception will be displayed with error code 403.
|
|
|
|
When configuring the [loginUrl|CWebUser::loginUrl] property, one can
|
|
provide a relative or absolute URL. One can also provide an array which
|
|
will be used to generate a URL by calling [CWebApplication::createUrl]. The
|
|
first array element should specify the
|
|
[route](/doc/guide/basics.controller#route) to the login controller
|
|
action, and the rest name-value pairs are GET parameters. For example,
|
|
|
|
~~~
|
|
[php]
|
|
array(
|
|
......
|
|
'components'=>array(
|
|
'user'=>array(
|
|
// this is actually the default value
|
|
'loginUrl'=>array('site/login'),
|
|
),
|
|
),
|
|
)
|
|
~~~
|
|
|
|
If the browser is redirected to the login page and the login is
|
|
successful, we may want to redirect the browser back to the page that
|
|
caused the authorization failure. How do we know the URL for that page? We
|
|
can get this information from the [returnUrl|CWebUser::returnUrl] property
|
|
of the user component. We can thus do the following to perform the
|
|
redirection:
|
|
|
|
~~~
|
|
[php]
|
|
Yii::app()->request->redirect(Yii::app()->user->returnUrl);
|
|
~~~
|
|
|
|
Role-Based Access Control
|
|
-------------------------
|
|
|
|
Role-Based Access Control (RBAC) provides a simple yet powerful
|
|
centralized access control. Please refer to the [Wiki
|
|
article](http://en.wikipedia.org/wiki/Role-based_access_control) for more
|
|
details about comparing RBAC with other more traditional access control
|
|
schemes.
|
|
|
|
Yii implements a hierarchical RBAC scheme via its
|
|
[authManager|CWebApplication::authManager] application component. In the
|
|
following ,we first introduce the main concepts used in this scheme; we
|
|
then describe how to define authorization data; at the end we show how to
|
|
make use of the authorization data to perform access checking.
|
|
|
|
### Overview
|
|
|
|
A fundamental concept in Yii's RBAC is *authorization item*. An
|
|
authorization item is a permission to do something (e.g. creating new blog
|
|
posts, managing users). According to its granularity and targeted audience,
|
|
authorization items can be classified as *operations*,
|
|
*tasks* and *roles*. A role consists of tasks, a task
|
|
consists of operations, and an operation is a permission that is atomic.
|
|
For example, we can have a system with `administrator` role which consists
|
|
of `post management` task and `user management` task. The `user management`
|
|
task may consist of `create user`, `update user` and `delete user`
|
|
operations. For more flexibility, Yii also allows a role to consist of
|
|
other roles or operations, a task to consist of other tasks, and an
|
|
operation to consist of other operations.
|
|
|
|
An authorization item is uniquely identified by its name.
|
|
|
|
An authorization item may be associated with a *business rule*. A
|
|
business rule is a piece of PHP code that will be executed when performing
|
|
access checking with respect to the item. Only when the execution returns
|
|
true, will the user be considered to have the permission represented by the
|
|
item. For example, when defining an operation `updatePost`, we would like
|
|
to add a business rule that checks if the user ID is the same as the post's
|
|
author ID so that only the author himself can have the permission to update
|
|
a post.
|
|
|
|
Using authorization items, we can build up an *authorization
|
|
hierarchy*. An item `A` is a parent of another item `B` in the
|
|
hierarchy if `A` consists of `B` (or say `A` inherits the permission(s)
|
|
represented by `B`). An item can have multiple child items, and it can also
|
|
have multipe parent items. Therefore, an authorization hierarchy is a
|
|
partial-order graph rather than a tree. In this hierarchy, role items sit
|
|
on top levels, operation items on bottom levels, while task items in
|
|
between.
|
|
|
|
Once we have an authorization hierarchy, we can assign roles in this
|
|
hierarchy to application users. A user, once assigned with a role, will
|
|
have the permissions represented by the role. For example, if we assign the
|
|
`administrator` role to a user, he will have the administrator permissions
|
|
which include `post management` and `user management` (and the
|
|
corresponding operations such as `create user`).
|
|
|
|
Now the fun part starts. In a controller action, we want to check if the
|
|
current user can delete the specified post. Using the RBAC hierarchy and
|
|
assignment, this can be done easily as follows:
|
|
|
|
~~~
|
|
[php]
|
|
if(Yii::app()->user->checkAccess('deletePost'))
|
|
{
|
|
// delete the post
|
|
}
|
|
~~~
|
|
|
|
### Configuring Authorization Manager
|
|
|
|
Before we set off to define an authorization hierarchy and perform access
|
|
checking, we need to configure the
|
|
[authManager|CWebApplication::authManager] application component. Yii
|
|
provides two types of authorization managers: [CPhpAuthManager] and
|
|
[CDbAuthManager]. The former uses a PHP script file to store authorization
|
|
data, while the latter stores authorization data in database. When we
|
|
configure the [authManager|CWebApplication::authManager] application
|
|
component, we need to specify which component class to use and what are the
|
|
initial property values for the component. For example,
|
|
|
|
~~~
|
|
[php]
|
|
return array(
|
|
'components'=>array(
|
|
'db'=>array(
|
|
'class'=>'CDbConnection',
|
|
'connectionString'=>'sqlite:path/to/file.db',
|
|
),
|
|
'authManager'=>array(
|
|
'class'=>'CDbAuthManager',
|
|
'connectionID'=>'db',
|
|
),
|
|
),
|
|
);
|
|
~~~
|
|
|
|
We can then access the [authManager|CWebApplication::authManager]
|
|
application component using `Yii::app()->authManager`.
|
|
|
|
### Defining Authorization Hierarchy
|
|
|
|
Defining authorization hierarchy involves three steps: defining
|
|
authorization items, establishing relationships between authorization
|
|
items, and assigning roles to application users. The
|
|
[authManager|CWebApplication::authManager] application component provides a
|
|
whole set of APIs to accomplish these tasks.
|
|
|
|
To define an authorization item, call one of the following methods,
|
|
depending on the type of the item:
|
|
|
|
- [CAuthManager::createRole]
|
|
- [CAuthManager::createTask]
|
|
- [CAuthManager::createOperation]
|
|
|
|
Once we have a set of authorization items, we can call the following
|
|
methods to establish relationships between authorization items:
|
|
|
|
- [CAuthManager::addItemChild]
|
|
- [CAuthManager::removeItemChild]
|
|
- [CAuthItem::addChild]
|
|
- [CAuthItem::removeChild]
|
|
|
|
And finally, we call the following methods to assign role items to
|
|
individual users:
|
|
|
|
- [CAuthManager::assign]
|
|
- [CAuthManager::revoke]
|
|
|
|
Below we show an example about building an authorization hierarchy with
|
|
the provided APIs:
|
|
|
|
~~~
|
|
[php]
|
|
$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');
|
|
~~~
|
|
|
|
> Info: While the above example looks long and tedious, it is mainly for
|
|
demonstrative purpose. Developers usually need to develop some user
|
|
interfaces so that end users can use to establish an authorization
|
|
hierarchy more intuitively.
|
|
|
|
|
|
### Using Business Rules
|
|
|
|
When we are defining the authorization hierarchy, we can associate a role, a task or an operation with a so-called *business rule*. We may also associate a business rule when we assign a role to a user. A business rule is a piece of PHP code that is executed when we perform access checking. The returning value of the code is used to determine if the role or assignment applies to the current user. In the example above, we associated a business rule with the `updateOwnPost` task. In the business rule we simply check if the current user ID is the same as the specified post's author ID. The post information in the `$params` array is supplied by developers when performing access checking.
|
|
|
|
|
|
### Access Checking
|
|
|
|
To perform access checking, we first need to know the name of the
|
|
authorization item. For example, to check if the current user can create a
|
|
post, we would check if he has the permission represented by the
|
|
`createPost` operation. We then call [CWebUser::checkAccess] to perform the
|
|
access checking:
|
|
|
|
~~~
|
|
[php]
|
|
if(Yii::app()->user->checkAccess('createPost'))
|
|
{
|
|
// create post
|
|
}
|
|
~~~
|
|
|
|
If the authorization rule is associated with a business rule which
|
|
requires additional parameters, we can pass them as well. For example, to
|
|
check if a user can update a post, we would do
|
|
|
|
~~~
|
|
[php]
|
|
$params=array('post'=>$post);
|
|
if(Yii::app()->user->checkAccess('updateOwnPost',$params))
|
|
{
|
|
// update post
|
|
}
|
|
~~~
|
|
|
|
|
|
### Using Default Roles
|
|
|
|
> Note: The default role feature has been available since version 1.0.3
|
|
|
|
Many Web applications need some very special roles that would be assigned to
|
|
every or most of the system users. For example, we may want to assign some
|
|
privileges to all authenticated users. It poses a lot of maintenance trouble
|
|
if we explicitly specify and store these role assignments. We can exploit
|
|
*default roles* to solve this problem.
|
|
|
|
A default role is a role that is implicitly assigned to every user, including
|
|
both authenticated and guest. We do not need to explicitly assign it to a user.
|
|
When [CWebUser::checkAccess] is invoked, default roles will be checked first as if they are
|
|
assigned to the user.
|
|
|
|
Default roles must be declared in the [CAuthManager::defaultRoles] property.
|
|
For example, the following configuration declares two roles to be default roles: `authenticated` and `guest`.
|
|
|
|
~~~
|
|
[php]
|
|
return array(
|
|
'components'=>array(
|
|
'authManager'=>array(
|
|
'class'=>'CDbAuthManager',
|
|
'defaultRoles'=>array('authenticated', 'guest'),
|
|
),
|
|
),
|
|
);
|
|
~~~
|
|
|
|
Because a default role is assigned to every user, it usually needs to be
|
|
associated with a business rule that determines whether the role
|
|
really applies to the user. For example, the following code defines two
|
|
roles, `authenticated` and `guest`, which effectively apply to authenticated
|
|
users and guest users, respectively.
|
|
|
|
~~~
|
|
[php]
|
|
$bizRule='return !Yii::app()->user->isGuest;';
|
|
$auth->createRole('authenticated', 'authenticated user', $bizRule);
|
|
|
|
$bizRule='return Yii::app()->user->isGuest;';
|
|
$auth->createRole('guest', 'guest user', $bizRule);
|
|
~~~
|
|
|
|
<div class="revision">$Id$</div> |