Preparando o ambiente para o Node.js
Felipe Batista
1872

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:

classe Usuario

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:

classe UsuarioController

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!

Ação que carrega o formulário de criação de Usuario no UsuarioController com ChoiceType

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:

view de criação de usuário (novo.html.twig)

Listagem:

view de listagem de usuario (lista.html.twig)

E, finalmente, edição:

view de edição de usuário (edita.html.twig)

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
comando de instalação do bundle 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

classe Usuario implementando a interface UserInterface

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.

security.yaml configurado para a classe Usuario

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
comando de instalação do maker bundle

O maker já traz toda a estrutura de acesso pronta com um simples comando

php bin/console make:auth
comando make:auth completo

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

security.yaml com authenticator gerado pelo maker

Gerou o controller que renderiza o login

SecurityController, responsável por renderizar a tela de login

O Twig com o formulário

login.html.twig

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

arquivo LoginFormAuthenticator.php gerado pelo maker

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

dump do parâmetro credentials

E, o segundo parâmetro, é o usuario encontrado na base, fornecido pelo provider que a gente configurou lá atrás =)

dump do parâmetro user

O próprio Authenticator já valida se o usuário digitado existe no banco de dados ou não

teste de usuário não existente na rota /login

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

checkCredentials implementado

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

exemplo de autenticação de usuário expirado

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

exemplo de implementação onAuthenticationSucess

No nosso caso, quando o usuário logar queremos que ele seja direcionado para a listagem de livros, do LivroController

LivroController, responsável pelo CRUD de livros

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

action com o name sobrescrito

E, agora, basta passar esse mesmo name lá pro redirect

onAuthenticationSuccess implementado

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

Usuário não logado acessando a listagem

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

Action cria com validação de permissão

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!