Autenticação com Zend Framework 1


Neste artigo explico detalhadamente os componentes de Autenticação do Zend Framework demonstrando como restringir acesso a uma aplicação, e como, baseado nas credenciais de um usuário, verificar se ele está apto ou não a acessar os recursos protegidos da aplicação.

Conceitos

Autenticação é o ato de determinar se uma entidade é aquilo que ela diz ser, baseado em suas credenciais. Explicando de uma forma mais clara, é o ato de verificar determinados dados de um usuário e, caso ele realmente seja um usuário válido da aplicação, identificá-lo para que a aplicação saiba quem ele é. No Zend Framework esta funcionalidade é fornecida pelo componente Zend_Auth, que além de prover uma API para autenticação, também inclui adaptadores concretos de autenticação para cenários comumente encontrados.

Um adaptador Zend_Auth é utilizado para fazer a autenticação em um serviço de autenticação específico como, por exemplo, LDAP, banco de dados e arquivos físicos. Cada adaptador terá certas particularidades de comportamentos e opções, porém algumas características básicas serão encontradas em todos os adaptadores, por exemplo aceitar credenciais de acesso, efetuar consultas no serviço de autenticação e retornar resultados. Os adaptadores implementam a interface Zend_Auth_Adapter_Interface, esta definindo o método authenticate(), que cuida da consulta utilizada na autenticação, que deverá ser implementado em cada um dos adaptadores. Para a utilização deste método, cada adaptador deverá estar devidamente configurado com os dados necessários na autenticação, como as opções específicas do adaptador e o usuário e senha que deverão ser utilizados.

Uma tentativa de autenticação utilizando o método authenticate() de um adaptador Zend_Auth irá retornar como resultado uma instância da classe Zend_Auth_Result devidamente populada com informações para validar o retorno da autenticação. Esta classe possui os seguintes métodos:

  • isValid() - Retorna true se e somente se o resultado representa uma tentativa de autenticação bem-sucedida;
  • getCode() - Retorna uma constante da classe Zend_Auth_Result para determinar que tipo de falha de autenticação ou sucesso que ocorreu. Pode ser usado em situações onde o desenvolvedor necessita saber o tipo de resultado ocorrido para proceder de alguma forma específica;
  • getIdentity() - Retorna a identidade da tentativa de autenticação, pode ser o nome de usuário ou alguma outra característica definida como identidade;
  • getMessages() - Retorna um array de mensagens sobre uma tentativade autenticação mal-sucedida.

As constantes que podem ser retornadas no método getCode() são listadas abaixo:

Zend_Auth_Result::SUCCESS //Sucesso na  autenticação
Zend_Auth_Result::FAILURE //Falha na autenticação
Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND  //Falha, a identidade é inválida
Zend_Auth_Result::FAILURE_IDENTITY_AMBIGUOUS  //Falha, a identidade é ambígua
Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID  //Falha, a senha é inválida
Zend_Auth_Result::FAILURE_UNCATEGORIZED  //Falha não categorizada

Efetuar corretamente uma autenticação que inclui suas devidas credenciais é útil por si só, mas também é importante manter a autenticação feita para não haver a necessidade de solicitar as credenciais de autenticação a cada requisição. O HTTP é um protocolo stateless, porém técnicas como cookies e sessões foram criadas para facilitar o mantimento de um determinado estado durante diversas requisições às aplicações. Por padrão o componente Zend_Auth armazena uma tentativa bem-sucedida de autenticação na sessão do PHP.  Ele utiliza uma classe de armazenamento chamada Zend_Auth_Storage_Session que utiliza a classe Zend_Session para manipular as sessões. Também pode-se utilizar para armazenamento uma classe customizada, para isso basta prover um objeto que implemente a classe Zend_Auth_Storage_Interface para o método Zend_Auth::setStorage().

Codificando a Autenticação

Após explicar detalhadamente os conceitos que envolvem autenticação é hora de codificar um exemplo simples que fará uso dos componentes do Zend Framework. Este exemplo precisará de uma tabela no banco de dados onde serão armazenados os usuários do sistema. Esta tabela está descrita a seguir:

CREATE TABLE `usuario`(
	id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
	login VARCHAR(30) NOT NULL UNIQUE,
	senha VARCHAR(60) NOT NULL,
	nome_completo VARCHAR(100) NOT NULL
);

É inserido então um usuário de exemplo que estará apto a se autenticar no sistema:

INSERT INTO `usuario` VALUES ('', 'admin', SHA1('admin'), 'Administrador');

É possível notar que é uma tabela extremamente simples, porém essa estrutura já é o suficiente para utilizar no exemplo desenvolvido. Para este exemplo foi criado um projeto padrão do Zend Framework com o seguinte comando:

zf create project zend_auth

Este projeto conterá um controlador de notícias, que seria um recurso apenas disponível para usuários autenticados no sistema, e outro controlador de autenticação, utilizado para fazer login e logout da aplicação. Para criar estes controladores e as ações de autenticação os seguintes comandos são utilizados:

cd zend_auth
zf create controller noticias
zf create controller auth
zf create action login Auth
zf create action logout Auth

O próximo passo é configurar o adaptador do banco de dados MySQL, e configurar o controlador de notícias como o controlador padrão da aplicação. No arquivo application.ini localizado na pasta application/configs basta acrescentar o seguinte conteúdo:

; Faz com que o controlador de notícias seja o controlador padrão
resources.frontController.defaultControllerName = "noticias"
; Banco de dados
resources.db.adapter = "PDO_MYSQL"
resources.db.params.host = "localhost"
resources.db.params.username = "root"
resources.db.params.password = ""
resources.db.params.dbname = "zend_auth"

Agora o último passo é configurar o Autoload de classes do Zend Framework. Para isso basta abrir o arquivo application/Bootstrap.php e adicionar o seguinte método na classe:

protected function _initAutoload()
{
    $autoloader = new Zend_Application_Module_Autoloader(array(
            'basePath' => APPLICATION_PATH,
            'namespace' => ''
    ));
    return $autoloader;
}

Formulário de Login

Todo sistema que precisa de autenticação fornece um formulário onde o usuário pode informar suas credenciais para se identificar no sistema. Para criar o formulário de login pode-se utilizar o componente Zend_Form, colocando os campos de login e senha. Para isto, primeiramente basta executar o seguinte comando:

zf create form login

Com isso um arquivo nomeado Login.php poderá ser encontrado dentro de application/forms. Para criar o formulário basta colocar o seguinte conteúdo neste arquivo:

<?php

class Form_Login extends Zend_Form
{
    public function init()
    {
        $this->setName('login');

        $login = new Zend_Form_Element_Text('login');
        $login->setLabel('Login:')
              ->setRequired(true)
              ->addFilter('StripTags')
              ->addFilter('StringTrim')
              ->addValidator('NotEmpty');

        $senha = new Zend_Form_Element_Password('senha');
        $senha->setLabel('Senha:')
              ->setRequired(true)
              ->addFilter('StripTags')
              ->addFilter('StringTrim')
              ->addValidator('NotEmpty');

        $submit = new Zend_Form_Element_Submit('submit');
        $submit->setLabel('Logar')
               ->setAttrib('id', 'submitbutton');

        $this->addElements(array($login, $senha, $submit));
    }
}

Neste código são criados os campos login e senha, onde ambos possuirão filtros que farão uma limpeza nos dados fornecidos pelo usuário, tirando espaços vazios e tags HTML, e uma validação de campos obrigatórios. Após criar estes elementos é criado o botão de envio e, por último, todos os elementos são adicionados ao corpo do formulário. Estudar mais afundo o componente Zend_Form foge do escopo deste artigo, só este componente pode ser assunto de vários outros artigos.

Ação de Login

Após criar o formulário é hora de implementar a ação de login. O seguinte código ficará encarregado de efetuar o login na aplicação:

public function loginAction()
{
    $this->_flashMessenger = $this->_helper->getHelper('FlashMessenger');
    $this->view->messages = $this->_flashMessenger->getMessages();
    $form = new Form_Login();
    $this->view->form = $form;
    //Verifica se existem dados de POST
    if ( $this->getRequest()->isPost() ) {
        $data = $this->getRequest()->getPost();
        //Formulário corretamente preenchido?
        if ( $form->isValid($data) ) {
            $login = $form->getValue('login');
            $senha = $form->getValue('senha');

            $dbAdapter = Zend_Db_Table::getDefaultAdapter();
            //Inicia o adaptador Zend_Auth para banco de dados
            $authAdapter = new Zend_Auth_Adapter_DbTable($dbAdapter);
            $authAdapter->setTableName('usuario')
                        ->setIdentityColumn('login')
                        ->setCredentialColumn('senha')
                        ->setCredentialTreatment('SHA1(?)');
            //Define os dados para processar o login
            $authAdapter->setIdentity($login)
                        ->setCredential($senha);
            //Efetua o login
            $auth = Zend_Auth::getInstance();
            $result = $auth->authenticate($authAdapter);
            //Verifica se o login foi efetuado com sucesso
            if ( $result->isValid() ) {
                //Armazena os dados do usuário em sessão, apenas desconsiderando
                //a senha do usuário
                $info = $authAdapter->getResultRowObject(null, 'senha');
                $storage = $auth->getStorage();
                $storage->write($info);
                //Redireciona para o Controller protegido
                return $this->_helper->redirector->goToRoute( array('controller' => 'noticias'), null, true);
            } else {
                //Dados inválidos
                $this->_helper->FlashMessenger('Usuário ou senha inválidos!');
                $this->_redirect('/auth/login');
            }
        } else {
            //Formulário preenchido de forma incorreta
            $form->populate($data);
        }
    }
}

Inicialmente é necessário instanciar o formulário de login e passá-lo para a camada View, para exibí-lo ao usuário. Caso este formulário seja enviado para processamento o método $this->getRequest()->isPost() irá retornar true, e então toda a lógica para efetuar o login será executada. Primeiramente os dados do formulário são recuperados através do método $this->getRequest()->getPost(), então é feita uma verificação se os dados foram preenchidos corretamente, caso eles não foram preenchidos, ele não entrará na lógica de login e executará o trecho:

} else {
    //Formulário preenchido de forma incorreta
    $form->populate($data);
}

Que basicamente irá preencher o formulário com os dados já enviados, para o usuário não precisar reinformá-los, permitindo exibí-los na View. No caso do formulário ser preenchido corretamente a lógica de autenticação é executada, onde são recuperados os valores preenchidos do formulário através do método getValue(atributo), e finalmente os componentes do Zend Framework entram em ação, primeiramente é recuperado o adaptador de banco de dados sendo executado pela aplicação e, então ele é passado para a classe Zend_Auth_Adapter_DbTable, que, após ser instanciada, é configurada para utilizar a tabela usuario do banco de dados, utilizando como Identidade a coluna login, e como Credencial a coluna senha, e como Tratamento de senha o formato de criptografia SHA1.

Após configurar o adaptador os dados informados pelo usuário são passados para o mesmo, em seguida o componente Zend_Auth é iniciado, e é efetuada uma tentativa de autenticação. Caso o resultado seja válido, é armazenado um objeto com os dados do usuário em sessão, excluindo apenas sua senha por questões de segurança, e o usuário é redirecionado para o controlador de notícias.  Caso o resultado seja inválido, é criada uma mensagem de erro através do componente FlashMessenger, que é instanciado no início desta action passando as possíveis mensagens armazenadas para a camada View, e então o usuário é redirecionado para o formulário de login, que conterá uma mensagem informando sobre o erro encontrado.

Agora a última etapa é codificar a View login.phtml, localizada na pasta application/views/scripts/auth, para exibir o formulário e as possíveis mensagens de erro. O código deste arquivo é apresentado a seguir:

<h2>Login</h2>
<?php echo ( sizeof( $this->messages ) > 0 ) ? $this->messages[0] : ""; ?>
<?php echo $this->form; ?>

Este arquivo é extremamente simples, ele terá uma verificação se existem mensagens de erro para exibir ao usuário, caso existam ele irá imprimí-las, e irá exibir o formulário de login. Como o componente Zend_Form, encapsula toda a validação, não é necessário mais nenhum código. Agora para testar basta apontar o browser para o projeto, no meu caso estou utilizando a seguinte url: http://zend_auth/auth/login. A seguinte imagem deverá ser exibida:

Agora a última etapa é configurar a ação indexAction para redirecionar para a ação de login. Para isso basta deixá-la da seguinte forma:

public function indexAction()
{
    return $this->_helper->redirector('login');
}

Logout

Efetuar logout com os componentes do Zend Framework é uma etapa extremamente simples. Na classe AuthController foi criada a ação logoutAction, basta alterá-la para que ela fique da seguinte forma:

public function logoutAction()
{
    $auth = Zend_Auth::getInstance();
    $auth->clearIdentity();
    return $this->_helper->redirector('index');
}

Basicamente é recuperada a instância do Zend_Auth e é feita uma limpeza dos dados do usuário logado, após isso o usuário é redirecionado ao formulário de login, para que ele realmente saiba que agora ele não está mais autenticado no sistema.

Controlador de notícias

O Controlador de notícias será o ponto de verificação de todo o processo de login. Neste exemplo ele é uma área restrita, portanto é necessário protegê-lo do acesso de usuários não-autenticados. O método init() é acessado antes de qualquer ação de um controlador, portanto ele é o ponto ideal para fazer a proteção do controlador. Para obter esta proteção basta modificar este método, no arquivo NoticiasController.php, deixando-o com o seguinte conteúdo:

public function init()
{
    if ( !Zend_Auth::getInstance()->hasIdentity() ) {
        return $this->_helper->redirector->goToRoute( array('controller' => 'auth'), null, true);
    }
}

Mais um código extremamente simples, ele irá verificar se existe alguma identidade autenticada no sistema, e, caso não exista, o usuário será redirecionado para o formulário de login.

Agora no método indexAction() será feita uma breve codificação para que a sua View possa informar os dados do usuário, junto com um link para efetuar o logout da aplicação. Este código fica da seguinte forma:

public function indexAction()
{
    $usuario = Zend_Auth::getInstance()->getIdentity();
    $this->view->usuario = $usuario;
}

Ele recupera a identidade armazenada na instância do Zend_Auth e passa-a para a camada de apresentação. A View desta ação está localizada em application/views/scripts/noticias/index.phtml e o seguinte conteúdo deve ser colocado nela:

<h2>Usuário logado: <?php echo $this->usuario->nome_completo; ?></h2>
<p><a href="<?php echo $this->url(array('controller' => 'auth', 'action' => 'logout'), null, true); ?>">Efetuar logout</a></p>

Esse código irá mostrar o nome do usuário logado e logo abaixo um link para efetuar logout do sistema. Após clicar neste link, pode-se verificar se o logout foi feito com sucesso tentando acessar novamente o controlador de notícias. O resultado será um redirecionamento para o formulário de login, o que garante que a aplicação está devidamente protegida.

Conclusão

Os componentes do Zend Framework são extremamente simples de se utilizar além de fornecerem uma enorme flexibilidade de extensão. Pode-se criar adaptadores customizados para o Zend_Auth, o que permite estender suas funcionalidades de acordo com cada cenário, tudo isso com um código reaproveitável e bem refinado. Neste artigo procurei abordar os conceitos que envolvem os componentes de autenticação, com um exemplo simples utilizando eles, de acordo com cada situação este exemplo precisará de uma boa refatoração para melhorar o fluxo e o reaproveitamento de código. Para isso recomendo a leitura do manual do Zend Framework, encontrado logo abaixo nas referências do artigo.

Referências