Back-End

18 mai, 2010

Autenticação com Zend Framework

Publicidade

Veremos aqui uma explicação detalhada dos 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, essa 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, como 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 implementada em cada um dos
adaptadores. Para a utilização desse 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. Essa 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 tentativa de 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. Esse exemplo precisará de uma tabela no banco de dados
onde serão armazenados os usuários do sistema. Essa 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
esse 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 esses 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 isso, 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));
}
}

Nesse 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 esses elementos é criado o botão de envio e,
por último, todos os elementos são adicionados ao corpo do formulário.
Estudar mais a fundo 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 exibi-lo ao usuário. Caso esse 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 exibi-los na View.
No caso de o 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 dessa 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 desse arquivo é
apresentado a seguir:

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

Esse arquivo é extremamente simples, ele terá uma verificação se
existem mensagens de erro para exibir ao usuário, caso existam ele irá
imprimi-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 essa
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. Esse 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 dessa ação está
localizada em application/views/scripts/noticias/index.phtml, e o
seguinte conteúdo deve ser colocado nela:

<h2>Usu&aacute;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-os. De acordo com cada situação esse 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