Symfony Login: autenticação e autorização
Um dos primeiros passos de qualquer desenvolvedor é conseguir criar, editar, listar e remover uma classe.
O segundo é proteger esses cadastros em uma área restrita, com login e senha, para que o sistema seja comercializável e/ou seguro. Afinal, a gente não quer que qualquer um altere nossos cadastros.
Ao longo desse post, vamos implementar um sistema de login em um CRUD de livros. Porém, apenas bibliotecários podem cadastrar enquanto atendentes só podem visualizar =)
Por onde começar?
Se vamos criar um sistema de login, precisamos criar, antes de mais nada, a representação de um usuário dentro do sistema.
Ou seja, precisamos de uma classe para guardar os dados de login, senha e permissões relacionadas ao sistema como qual usuário é administrador, etc.
Cada sistema pode ter uma classe diferente representando o usuário, dependendo da regra de negócio. Por exemplo, em uma escola os usuários podem ser Professor, Aluno, etc.
Já em um sistema de gestão de lançamento de horas, provavelmente a classe Funcionário exerceria esse papel. Pois são eles quem usam o sistema.
Mãos na massa
No nosso caso, vamos ficar com a classe Usuario por ser uma representação mais genérica e abrangente, sem regra de negócio:
Além da classe, guardando login, senha e as permissões é extremamente importante ter uma forma de cadastrar, listar, remover e atualizar os usuários.
CRUD de Usuario
Pra isso, a gente tem o CRUD de Usuario com um Controller centralizando as funcionalidades:
Aqui, interessante já indicar pro FormType do Symfony que o usuário pode ter mais de uma permissão e quais são as opções nas ações de cadastro e edição.
Pra isso, a gente pode usar o ChoiceType, que renderiza um HTML bem legal para seleção múltipla escolha
Como configurar o ChoiceType
O ChoiceType recebe como parâmetro algumas configurações indicando que queremos a seleção múltipla escolha com o “multiple” => true. Se você não quiser a seleção de mais de uma permissão é só trocar essa propriedade para false =)
Além disso, podemos indicar quais são as opções que serão exibidas com o “choices” => ["chave" => "ROLE_VALOR"]. Sendo a chave do array associativo o texto que aparece na página e o valor do array associativo será enviado para o banco de dados!
Isso é bem vantajoso no nosso contexto (e em muitos outros) porque aqui temos toda a liberdade de modelar os níveis de permissão do sistema sem muita burocracia e com segurança, pois o Symfony valida e restringe o formulário a apenas essas opções!
Criando as telas
Com as actions prontas, só precisamos montar as views de criação:
Listagem:
E, finalmente, edição:
Com isso, a gente já consegue criar os usuários que a gente quiser, com as os níveis de permissão que a gente quer!
Beleza, e como a gente usa esse Usuario?
Para saber se o Usuario tem acesso ao sistema ou não, a gente precisaria guardar um cookie no navegador de quem acessou o sistema apontando pra sessão no servidor com um token criptografado para garantir a segurança.
Esse processo é o que a gente chama de autenticação e a arquitetura pra isso pode ser bem complexa.
Security Bundle
Para nossa alegria, o Symfony tem um bundle chamado Security para lidar com segurança no geral, inclusive autenticação!
Para instalar o bundle e usar toda a arquitetura de segurança do framework, basta pedir para o composer:
composer req security
A única coisa que a gente precisa fazer, é dizer pro framework qual das nossas classes representa o usuário do sistema.
O que é UserInterface?
Para representar um usuário, a galera do Symfony criou a interface chamada UserInterface que garante alguns métodos para resgatar o username (login), password (senha) e roles(permissões).
Por isso, vamos começar implementando a interface na nossa classe Usuario
Apontando nos métodos da interface qual é o campo referente a login, senha e permissões pra gente nos métodos getUsername, getPassword e getRoles respectivamente.
Agora que a gente tem uma classe conversando com o framework, é só configurar o xml de segurança: security.yml
Configurações de segurança
O arquivo possui algumas configurações para encontrar e manusear nosso usuário, como a indicação dos encoders, que apontam qual a forma de criptografia que nós estamos usando na senha de cada usuário do sistema (no nosso caso, não estamos usando criptografia alguma na senha).
Por isso ele recebe a classe e aponta o algoritmo (algorithm) de criptografia.
Além do encoders, um muito importante é o providers, responsável por indicar finalmente ao framework quais classes temos disponíveis para login (lembrando que elas precisam implementar UserInterface) e apontar qual a propriedade de login de cada usuário do sistema.
Além disso os providers são utilizados pelo framework para recarregar os dados do usuário da sessão, aquele trabalho chato que a gente não quer fazer manualmente. Ou seja, é nele que toda magia acontece =)
Perfeito, agora temos toda a arquitetura de login e autenticação prontas, porém, em nenhum lugar falamos por onde o usuário vai entrar no sistema ou o que deve estar protegido ou não!
Separando os ambientes
Para separar os ambientes do que é público ou não, vamos precisar criar uma tela na qual o usuário digite seu email e sua senha e seja disparado o processo de autenticação do Symfony.
Normalmente essa tela é a tela de login que costuma ter um formulário com os campos Login e Senha.
Além disso, é necessário um controller para receber essa requisição e finalmente jogar o usuário pro processo de autenticação.
Maker Bundle
Felizmente, na versão 4 do Symfony, a gente tem disponível o bundle maker, uma ferramenta para auxiliar a gente gerando alguns códigos repetitivos!
Para instalar o maker basta pedir pro composer pelo comando
composer req symfony/maker-bundle --dev
O maker já traz toda a estrutura de acesso pronta com um simples comando
php bin/console make:auth
O comando gera um controller já com a action de login, os templates necessários para renderizar o formulário e um Guard authenticator já isolado para processar o envio do formulário e autenticar!
A gente só precisa dar o nome das classes que vão ser geradas, conforme o comando pedir. No nosso caso, a gente deu o nome LoginFormAuthenticator pro Authenticator e o nome SecurityController pro controller.
Completando o código gerado
Se a gente der uma olhada, o maker já até atualizou o arquivo de configuração de segurança, apontando nosso authenticator
Gerou o controller que renderiza o login
O Twig com o formulário
Com um bloco comentado já pronto com a funcionalidade de "lembrar de mim"!
Se você quiser essa funcionalidade, é só descomentar se não quiser é importante excluir pra não deixar código sobrando no projeto.
E, finalmente, o LoginFormAuthenticator
Com todos os métodos de gestão do usuário na sessão já prontos!
A única coisa que a gente precisa fazer é realizar a verificação se aquele usuário está valido no método checkCredentials e indicar pra onde ele vai ser redirecionado após a validação no método onAuthenticationSuccess
CheckCredentials
O método recebe dois parâmetros o primeiro $credentials é injetado pelo framework com os valores digitados no formulário de login na seguinte estrutura
E, o segundo parâmetro, é o usuario encontrado na base, fornecido pelo provider que a gente configurou lá atrás =)
O próprio Authenticator já valida se o usuário digitado existe no banco de dados ou não
A única coisa que a gente precisa fazer é validar a senha
Ou seja, verificar se a senha que o sujeito digitou é igual a senha do nosso usuário, retornando verdadeiro caso seja igual e falso caso não seja
A grande vantagem aqui, é que se nosso sistema crescer e precisarmos, por exemplo, garantir que nenhum Usuario expirado tenha acesso ao sistema bastaria incrementar a verificação de credentials
E assim, qualquer usuário que estiver expirado perde automaticamente o acesso ao sistema.
onAuthenticationSuccess
Aqui, a estrutura é bem parecida com a de um Controller.
A ideia é redirecionar o usuário que acabou de logar, pra isso a gente retorna uma instância de RedirectResponse
No nosso caso, quando o usuário logar queremos que ele seja direcionado para a listagem de livros, do LivroController
Seguindo o padrão de nomenclatura das actions do framework, teremos o name app_livro_lista, mas, nada nos impede de sobrescrever esse nome lá na action
E, agora, basta passar esse mesmo name lá pro redirect
Agora, ao fazer login, com o usuário e a senha corretos, a gente já cai direto na listagem de Livros com nosso usuário autenticado na sessão com o cookie criptografado!
Porém, nada impede um usuário não logado de acessar essa funcionalidade ainda
A única coisa que falta pro nosso Login ficar perfeito é indicar em algum lugar, quais permissões tem acesso a quais páginas.
Restringindo o acesso
Para isso, existem diversas formas de impedir o acesso do usuário a uma página.
Uma bem simples é chamar o método que verifica se o usuário logado tem permissão dentro do próprio controller.
Então, se a gente quer dizer que qualquer usuário que não seja bibliotecário não pode acessar a action de criação de livros, basta chamar no controller
Esse procedimento de restringir acessos, seja onde for, é o que a gente chama de autorização.
Para saber mais: Outra maneira de restringir acesso
Além do if no controller, é possível configurar o nível de acesso de cada action usando anotações!
Próximos passos
Com os bundles Security e o Maker a gente já consegue fazer bastante coisa em termos de autenticação e autorização. No próximo post, vamos falar sobre como fazer login com o facebook no Symfony.
E ai, o que achou dos bundles? Conhecem outras formas de garantir permissão pros usuários? Compartilha com a gente aqui nos comentarios=)
Ah, o código pronto desse post, você encontra lá no meu git!