Autenticação e Autorização

Autenticação e autorização são necessárias para uma página Web que deve ser limitada a determinados usuários. Autenticação serve para verificar se alguém é quem diz ser. Isto geralmente envolve a utilização de um usuário e
senha, mas pode incluir outros métodos de validação da identidade, como cartão inteligente (smart card), impressões digitais, etc. Autorização serve para descobrir se uma pessoa, uma vez identificada (e autenticada), tem permissão para manipular recursos específicos. Isto geralmente é utilizado para descobrir se esta pessoa está em uma regra específica que possui acesso aos recursos.

O Yii tem embutido um framework de autenticação/autorização (auth) que é fácil de usar e pode ser customizado para necessidades específicas.

A parte central no framework auth do Yii é a pré-declaração do componente de aplicação do usuário que é um objeto implementando a interface IWebUser. O componente de usuário representa a persistência da informação de identidade para o usuário corrente. Pode ser acessado de qualquer lugar, utilizando Yii::app()->user.

Utilizando o componente de usuário, podemos verificar se um usuário está conectado ou não através de CWebUser::isGuest; podemos conectar e desconectar um usuário; podemos verificar se um usuário pode executar operações específicas, chamando CWebUser::checkAccess; e podemos também obter o identificador único e outras informações armazenadas sobre a identidade do usuário.

1. Definindo a Classe de Identidade

Como mencionado acima, autenticação é como validar a identidade do usuário. A autenticação em uma aplição Web típica, geralmente é realizada pela combinação de usuário e senha, para verificar a identidade do usuário. Entretanto, pode ser incluído outros métodos e implementações diferentes podem ser necessárias. Para prover os diferentes métodos de autenticação, o framework auth do Yii introduz a classe de identidade.

Podemos definir uma classe de identidade que contenha a autenticação lógica atual. A classe de identidade deve implementar a interface IUserIdentity. Diferentes classes de identidade podem ser implementadas para diferentes abordagens de autenticação (por exemplo, OpenID, LDAP, Twitter OAuth, Facebook Connect). Um bom começo quando escrevermos nossa própria implementação é extender a classe CUserIdentity que é uma classe básica para o modelo de autenticação utilizando usuário e senha.

O principal trabalho na definição da classe de identidade é a implementação do método IUserIdentity::authenticate. Este método é utilizando para encapsular os principal detalhes da abordagem da autenticação. Uma classe de identidade também pode declarar informações adicionais de identidade que precisam ser armazenadas durante a sessão do usuário.

Um Exemplo

No exemplo a seguir, utilizamos uma classe de identidade para demonstrar a abordagem de autenticação para banco de dados. Isto é muito comum em aplicações Web. O usuário informa um usuário e senha em um formulário de login, e então validamos a credencial utilizando ActiveRecord, comparando em uma tabela de usuário no banco de dados. Há algumas coisas sendo demonstradas neste simples exemplo:

  1. A implementação do método authenticate() para utilizar banco de dados para validação das credenciais.
  2. Sobreescrita do método CUserIdentity::getId() para retornar a propriedade _id, porque na implementação padrão retorna o nome de usuário como ID.
  3. Utilização do método setState() (CBaseUserIdentity::setState) para demonstrar o armazenamento de outras informações que podem ser facilmente recuperadas em requisições posteriores.
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;
    }
}

Quando falarmos sobre login e logout na próxima seção, veremos que passamos a classe identidade como parâmetro ao método login de um usuário. Qualquer informação que precisarmos armazenar no estado (apenas chamando CBaseUserIdentity::setState)) serão passadas para a classe CWebUser, que por sua vez irá armazenar de forma persistente, como uma sessão. Esta informação pode ser acessada como uma propriedade da classe CWebUser. No nosso exemplo, armazenamos a informação do título do usuário através de $this->setState('title', $record->title);. Uma vez finalizado o processo de login, podemos obter a informação title do usuário corrente, simplesmente usando Yii::app()->user->title.

Info: Por padrão, CWebUser utiliza sessão para armazenamento persistente de informação sobre identidade de usuário. Caso o login estiver ativo e baseado na utilização de cookies (a configuração CWebUser::allowAutoLogin for verdadeira), a informação de identidade do usuário também será salva como um cookie. Tenha certeza de não declarar informações sensíveis (por exemplo, senha).

2. Login e Logout

Agora que já vimos um exemplo de criação de identidade de usuário, usaremos isto para ajudar a avaliar a implementação das ações de login e logout que necessitamos. O código a seguir demonstra como isto é feito:

// Login de um usuário com usuário e senha fornecidos.
$identity=new UserIdentity($username,$password);
if($identity->authenticate())
    Yii::app()->user->login($identity);
else
    echo $identity->errorMessage;
......
// Logout do usuário corrente
Yii::app()->user->logout();

Estamos criando um novo objeto de UserIdentity e passando as credenciais de autenticação (ou seja, os valores de $username e $password enviados pelo usuário) para o construtor. Então simplesmente chamamos o método authenticate(). Se bem sucedido, passamos a informação de identidade no método CWebUser::login, e armazenamos a informação de identidade em um armazenamento persistente (Por padrão, sessão do PHP) para recuperação em uma requisição subsequente. Caso a autenticação falhe, podemos verificar a propriedade errorMessage para obter mais informações sobre a falha.

Seja autenticado ou não, um usuário pode ser verificado em qualquer parte do aplicativo usando Yii::app()->user->isGuest. Se usarmos armazenamento persistente como sessão (o padrão) e/ou cookie (discutiremos abaixo) para armazenar a informação de identidade, o usuário pode ficar conectado e responder as requisições subsequentes. Neste caso, não precisamos utilizar a classe UserIdentity e todo o processo de login em casa solicitação. Por sua vez a classe CWebUser irá cuidar automaticamente do carregamento das informações de identidade do armazenamento persistente e podemos utilizá-lo para determinar se Yii::app()->user->isGuest retorna verdadeiro (true) ou falso (false).

Por padrão, um usuário será desconectado após determinado período de inatividade, que depende da configuração da sessão. Para alterar este comportamento, podemos setar a propriedade allowAutoLogin do componente de usuário para verdadeiro (true) e passar o parâmetro duração para o método CWebUser::login. O usuário permanecerá conectado para a duração especificada mesmo que ele feche a janela do navegador. Note que esta funcionalidade requer que o navegador do usuário aceite cookies.

// Manter o usuário conectado por 7 dias.
// Tenha certeza de que allowAutoLogin está setado como verdadeiro (true) no componente de usuário.
Yii::app()->user->login($identity,3600*24*7);

Conforme mencionado acima, quando o login baseado em cookie estiver habilitado, os estados armazenados através de CBaseUserIdentity::setState serão gravados no cookie. Na próxima vez que o usuário conectado entrar, estes estados serão lidos do cookie e preparados para serem acessados através de Yii::app()->user.

Embora o Yii possua medidas para previnir que o estado do cookie sejam alterados no lado do cliente, sugerimos fortemente que informações sensíveis de seguranção não sejam armazenadas como estados. Em vez disso, estas informações devem ser restauradas no lado do servidor através da leitura de algum armazenamento persistente (por exemplo, banco de dados).

Além disso, para qualquer aplicação Web séria, recomendamos a utilização das estratégias a seguir para aumentar a segurança de login baseado em cookie.

  • Quando um usuário efetua login com sucesso através de um formulário de login, geramos e armazenamos uma chave aleatória tando no estado do cookie quanto no armazenamento persistente do lado do servidor (por exemplo, banco de dados).

  • Mediante a uma requisição subsequente, quando a autenticação do usuário estiver sendo realizada via informação de cookie, podemos comparar as duas cópias da chave aleatória e assegurar uma validação anterior a entrada do usuário.

  • Se o usuário realizar login através de formulário novamente, a chave precisa ser regerada.

Utilizando a estratégia acima, eliminamos a possibilidade que determinado usuário reutilize um estado antigo de um cookie, que possa conter informações de estado desatualizadas.

Para implementar a estratégia acima, precisamos sobreescrever os dois métodos a seguir:

  • CUserIdentity::authenticate(): nele é onde a autenticação real é realizada. Se o usuário é autenticado, podemos regerar uma chave aletória e armazená-la na base de dados, bem como no estado da identidade com CBaseUserIdentity::setState.

  • CWebUser::beforeLogin(): nele é realizado a chamada quando o usuário será sendo conectado. Devemos verificar se a chave obtida do estado do cookie é a mesma que a do banco de dados.

4. Filtro de Controle de Acesso (Access Control Filter)

Filtro de controle de acesso é um esquema de autorização preliminar que verifica se o usuário atual pode realizar a ação solicitada. A autorização é baseada no nome do usuário, endereço de IP do cliente e tipos de requisição. É fornecido como um filtro chamado "accessControl".

Dica: Filtros de controle de acesso são suficientes para cenários simples. Para controle de acesso mais complexo você pode usar o controle de acesso baseado em papéis de usuários (role-based access (RBAC)).

Para controlar o acesso a ações em um controller nós instalamos o filtro de controle por sobrepor CController::filters (veja Filter) para mais detalhes sobre a instalação).

class PostController extends CController
{
    ......
    public function filters()
    {
        return array(
            'accessControl',
        );
    }
}

Acima especificamos que o filtro de controle de acesso deve ser aplicado a todas as ações de PostController. As regras detalhadas de autorização usadas pelo filtro são especificadas por sobrepor CController::accessRules na classe controller.

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('*'),
            ),
        );
    }
}

O código acima especifica três regras, cada uma representada por um array. O primeiro elemento do array é um 'allow' ou 'deny' e o segundo é formado por pares do tipo nome-valor que especificam o padrão dos parâmetros da regra. As regras definidas acima são interpretadas como se segue: as ações create e edit não podem ser executadas por usuários anônimos (não identificados); a ação delete pode ser executada por usuários com o papel admin; e a ação delete não pode ser executada por ninguém.

As regras de acesso são avaliadas uma a uma na ordem em que foram especificadas. A primeira regra que bater com o padrão atual (isto é, nome de usuário, papéis, IP do cliente, endereço) determina o resultado da autorização. Se esta regra for um allow a ação pode ser executada. Se for um deny a ação não pode ser executada; E se nenhuma das regras bater, a ação também poderá ser executada.

Dica: Para garantir que uma ação não seja executada sob certos contextos, é benéfico sempre especificar uma regra pega-tudo do tipo deny no final do conjunto de regras, como no exemplo:

return array(
    // ... outras regras...
    // a seguinte regra nega a ação 'delete' em todos os contextos
    array('deny',
        'actions'=>array('delete'),
    ),
);

Sem esta regra, se nenhuma das regras batesse em algum contexto, a ação delete ainda seria executada.

Uma regra de acesso pode bater nos seguintes contextos:

  • actions: especifica com quais ações esta regra bate. Deve ser um array de IDs de ações. A comparação não diferencia maiúsculas/minúsculas (case-insensitive).

  • controllers: especifica com quais controllers esta regra bate. Deve ser um array de IDs de ações. A comparação não diferencia maiúsculas/minúsculas (case-insensitive).

  • users: especifica com quais usuários esta regra bate. O nome de usuário é usado para a comparação. A comparação não diferencia maiúsculas/minúsculas (case-insensitive). Três caracteres especiais podem ser usados aqui:

    • *: qualquer usuário, inclusive anônimos e autenticados.
      • ?: usuários anônimos.
    • @: usuários autenticados.
  • roles: especifica com quais papéis esta regra bate. Isto faz uso do controle de acesso baseado em papéis, característica que será descrita na próxima sub-seção. Em resumo, a regra é aplicada se CWebUser::checkAccess retornar true para um dos papéis. Note que você deve usar principalmente papéis em uma regra allow por que, por definição, um papel representa uma permissão de fazer algo. Note também que, embora usemos o termo 'papéis' aqui, seu valor pode realmente ser qualquer item de autenticação, incluindo papéis, tarefas e operações.

  • ips: especifica com quais endereços de IP de clientes esta regra bate.

  • verbs: especifica com quais tipos de requisição (exemplo: GET, POST) esta regra bate. A comparação não diferencia maiúsculas/minúsculas (case-insensitive).
  • expression: especifica uma expressão PHP cujo valor indica se esta regra bate. Na expressão você pode a variável $user que se refere a Yii::app()->user.

5. Manipulando o Resultado da Autorização

Quando a autorização falha, ou seja, o usuário não tem permissão de realizar a ação especificada, um dos dois cenários a seguir pode ocorrer:

  • Se o usuário não estiver logado e se a propriedade loginUrl do componente User estiver configurada para ser a URL da página de login, o navegador vai ser redirecionado para essa página. Note que, por padrão, loginUrl aponta para a página site/login.

  • Caso contrário, uma exceção HTTP com código de erro 403 vai ser exibida.

Ao configurar a propriedade loginUrl, pode-se fornecer uma URL relativa ou absoluta. Pode-se também fornecer um array que vai ser usado para gerar uma URL por chamar CWebApplication::createUrl. O primeiro elemento array deve especificar a rota para a ação login do controller, e o restante, pares nome-valor de parâmetros GET. Por exemplo:

array(
    ......
    'components'=>array(
        'user'=>array(
            // este é, de fato, o valor padrão
            'loginUrl'=>array('site/login'),
        ),
    ),
)

Se o navegador for redirecionado para a página de login e o login for bem-sucedido, podemos redirecioná-lo novamente para a página que gerou a falha de autorização. Como podemos saber a URL dessa página? Podemos conseguir essa informação da propriedade returnUrl do componente Usuário. Podemos assim fazer o seguinte para executar o redirecionamento:

Yii::app()->request->redirect(Yii::app()->user->returnUrl);

Role-Based Access Control

6. Controle de Acesso Baseado em Papéis

Controle de Acesso Baseado em Papéis (Role-Based Access Control - RBAC) provê um simples porém poderoso controle de acesso centralizado. Por favor, consulte o [Artigo Wiki] (http://en.wikipedia.org/wiki/Role-based_access_control) para mais detalhes sobre a comparação do RBAC com outras formas de controle de acesso mais tradicionais.

O Yii implementa o esquema de hierarquia RBAC através de seu componente de aplicação authManager. A seguir, nós primeiro introduzimos os conceitos principais usados neste esquema; Após, descrevemos como definir dados de autorização. Por fim, mostramos como fazer uso dos dados de autorização para realizar a verificação de acesso.

Visão Geral

Um conceito fundamental do RBAC no Yii é o item de autorização. Um item de autorização é uma permissão de fazer algo (ex: criar novas postagens num blog, gerenciar usuários, etc). De acordo com sua granulidade e audiência, itens de autorização podem ser classificados como operações, tarefas e papéis. Um papel consiste de tarefas, uma tarefa consiste de operações e uma operação é uma permissão que é atômica. Por exemplo, podemos ter um sistema com um papel administrador que consista das tarefas gerenciar postagens e gerenciar usuários. A tarefa gerenciar usuários pode consistir das operações criar usuário, atualizar usuário e excluir usuário. Para maior flexibilidade o Yii também permite que um papel seja constituído de outros papéis e/ou operações, uma tarefa seja constituída de outras tarefas e uma operação seja constituída de outras operações.

Um item de autorização é identificado exclusivamente por seu nome.

Um item de autorização pode ser associado a uma business rule (regra de negócio). Uma business rule é um código em PHP que vai ser executado quando ocorrer a verificação de acesso com respeito ao item. O usuário terá a permissão de acesso representada pelo item somente quando a execução retornar true. Por exemplo, ao definir uma operação updatePost (atualizarPost), nós gostaríamos de adicionar uma business rule que verifique se ID do usuário é o mesmo que do autor do post de modo que somente o próprio autor possa ter permissão de atualizar um 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 multiple 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:

if(Yii::app()->user->checkAccess('deletePost'))
{
    // delete the post
}

7. Configuring Authorization Manager

Before we set off to define an authorization hierarchy and perform access checking, we need to configure the 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 application component, we need to specify which component class to use and what are the initial property values for the component. For example,

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 application component using Yii::app()->authManager.

8. 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 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:

Once we have a set of authorization items, we can call the following methods to establish relationships between authorization items:

And finally, we call the following methods to assign role items to individual users:

Below we show an example about building an authorization hierarchy with the provided APIs:

$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');

Once we have established this hierarchy, the authManager component (e.g. CPhpAuthManager, CDbAuthManager) will load the authorization items automatically. Therefore, we only need to run the above code one time, and NOT for every request.

Info: While the above example looks long and tedious, it is mainly for demonstrative purpose. Developers will usually need to develop some administrative user interfaces so that end users can use to establish an authorization hierarchy more intuitively.

9. 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:

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 pass in the post data in the $params:

$params=array('post'=>$post);
if(Yii::app()->user->checkAccess('updateOwnPost',$params))
{
    // update post
}

Using Default Roles

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.

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.

$bizRule='return !Yii::app()->user->isGuest;';
$auth->createRole('authenticated', 'authenticated user', $bizRule);
 
$bizRule='return Yii::app()->user->isGuest;';
$auth->createRole('guest', 'guest user', $bizRule);
$Id: topics.auth.txt 2890 2011-01-18 15:58:34Z qiang.xue $

Be the first person to leave a comment

Please to leave your comment.