OO na Prática: representando o usuário logado no sistema
Melhore o design e clareza do seu código de autenticação com uso da orientação a objetos
Praticamente toda aplicação Web implementa algum tipo de segurança a fim de proteger e garantir o acesso as informações. Para maioria das aplicações uma autenticação do tipo login&senha e bloqueio de páginas é suficiente. Como lógica geral, basta o usuário entrar com suas credencias, ter seus dados validados e a aplicação já coloca suas informações na sessão do servidor; a partir de agora sempre que for necessário validar o usuário logado basta verificar se existe uma instância do mesmo na sessão. Não parece difícil, não é? Apesar de simples, é comum ver projetos implementando essa lógica de tal forma que dificulta a manutenção e evolução do código e que abre brechas para sobrecarregar a sessão do servidor.
Independente do framework MVC, a autenticação se resume a uma página de login e um controller para validar os dados informados pelo usuário. A dificuldade não é implementar a página ou a lógica em si, mas sim decidir onde guardar os dados do usuário logado quando o login ocorrer com sucesso: no controller ou direto na sessão?
Para você ter idéia, no caso do JSF, é muito comum os desenvolvedores optarem por guardar o usuário logado dentro do próprio managed bean:
@Named
@SessionScoped // controller na sessão
public class LoginBean implements Serializable {
private String login;
private String senha;
private Usuario usuarioLogado; // informações do usuário logado
@Inject
private Autenticador autenticador;
@Inject
private FacesUtils facesUtils;
public String logar() {
Usuario usuario = autenticador.autentica(login, senha);
if (usuario != null) {
this.usuarioLogado= usuario; // preenche usuário na sessão
return "/pages/home?faces-redirect=true";
}
facesUtils.erro("Login ou senha inválido.");
return null;
}
// getters e setters
}
Veja que nessa abordagem o managed bean é mantido na sessão, configurado via anotação @SessionScoped
do CDI. Com o managed bean em sessão qualquer atributo dentro dele também é mantido no mesmo escopo. Apesar de ser uma das soluções mais utilizadas, ela apresenta alguns problemas difíceis de enxergar a primera vista.
Não guarde o usuário logado no managed bean
A solução anterior funcionará sem problemas, no entanto há um problema sútil quanto a sua implementação. Um managed bean existe para refletir o estado e o comportamento das nossas páginas,e este é o papel do LoginBean
: ser um reflexo da tela de login. Lembrar desse princípio faz toda a diferença ao trabalhar com JSF.
Se a página de login mudar, como por exemplo, adicionando uma combobox com uma lista de empresas para que o usuário selecione uma empresa antes de logar, bem ao estilo de login multi-empresa, o managed bean também mudaria, e o pior, ele poderia acabar guardando diversos outros objetos na sessão sem necessidade:
@Named
@SessionScoped // controller na sessão
public class LoginBean implements Serializable {
// outros atributos
private List<Empresa> empresas;
@PostConstruct
public void init() {
this.empresas = empresaDao.listaTudo();
}
}
Agora imagine seu sistema com centenas de usuários logados onde cada um deles tenha a mesma lista de empresas na sessão? Vou mais longe, imagine que cada empresa tem uma lista de departamentos e esta por sua vez uma lista de funcionários. Não há memória no mundo que aguente tamanho deslize do desenvolvedor.
Outro problema também implícito está relacionado ao acoplamento entre classes. Por exemplo, imagine que precisamos das informações do usuário logado em outro managed bean ou mesmo num Service ou DAO, como fazer? Para o CDI seria fácil, bastaria injetar a LoginBean
onde necessário e em seguida recuperar o usuário logado:
package br.com.triadworks.app.service;
@ApplicationScoped
public class RelatorioDeVendasService {
@Inject
private LoginBean loginBean; // oops! isso não é legal!
public File gerar(Date inicio, Date fim) {
Usuario usuarioLogado = loginBean.getUsuarioLogado();
// faz lógica para gerar relatório
}
}
Perceba o que acabamos de fazer: nós violamos a arquitetura em camadas. Uma classe da camada de baixo está referenciando uma classe da camada de cima. Sem notar, estamos voltando ao modelo espagueti, onde qualquer objeto acessa arbitrariamente outro. Estamos acoplando a camada de negócio a camada de apresentação, o que vai contra as boas práticas.
Repare que nosso pecado não tem a ver com tecnologias, mas sim por não percebermos que existe um conceito importante dentro do sistema, o conceito do Usuário Logado. Entidade clara no domínio de segurança mas casualmente ignorada por nós desenvolvedores. Perceber sua existência no sistema é o pulo do gato!
Representando o Usuário Logado numa classe
A todo instante falamos em "usuário logado" mas em nenhum momento o enxergamos como um abstração que pode (e deve) ser representada como uma classe. Afinal de contas, ele é um conceito que passa por todas as camadas da aplicação. O ideal é que tivéssemos uma representação melhor definida do usuário logado no sistema, dessa forma poderíamos isolá-lo em uma classe mais coesa, como abaixo:
@Named
@SessionScoped
public class UsuarioLogado implements Serializable {
private Usuario usuario; // dados do usuário
// getter e setter
}
Repare que nossa classe passa a ser gerenciada pelo CDI, dessa forma podemos injetá-la em outros objetos da aplicação. Uma das vantagens de isolar a representação do usuário logado numa classe é que podemos adicionar dados e comportamentos pertinentes a segurança nela. Desse modo, após um login bem sucedido, podemos guardar as informações do usuário do sistema dentro da instância de UsuarioLogado
.
Tendo esta representação do usuário logado definida, o próximo passo é configurar o managed bean LoginBean
para o escopo apropriado e, por fim, injetar a instância de UsuarioLogado
no managed bean, como abaixo:
@Named
@RequestScoped // controller em escopo de request
public class LoginBean {
// outros atributos
@Inject
private UsuarioLogado usuarioLogado;
public String logar() {
Usuario usuario = autenticador.autentica(login, senha);
if (usuario != null) {
this.usuarioLogado.setUsuario(usuario); // preenche usuário na sessão
return "/pages/home?faces-redirect=true";
}
facesUtils.erro("Login ou senha inválido.");
return null;
}
}
Por se tratar de um bean gerenciado pelo CDI, podemos utilizá-lo diretamente nas nossas páginas como qualquer outro bean anotado com @Named
:
<h3>Seja bem vindo, #{usuarioLogado.usuario.nome}.</h3>
Outra vantagem importante dessa abordagem é que ela nos permite delegar todo o gerenciamento do ciclo de vida do objeto para o container de IoC/DI, o que abre inúmeras possibilidades para estendê-la e integrá-la com outros componentes dentro da aplicação de maneira totalmente transparente. Isso traz ganhos consideráveis na hora de manter código, pois toda lógica está centrada num único local e integrada a todos os pontos importantes do sistema.
Abstraindo comportamentos
O que realmente gosto dessa implementação é que ela me permite criar métodos coesos dentro da classe, nesse caso métodos que refletem lógicas de segurança usadas no sistema, como logar, deslogar e verificar permissões e tipos de usuário. Falando em dar nomes melhores, poderíamos renomear o método setUsuario()
para algo mais semântico, como loga()
ou mesmo criar um método para verificar se o usuário logado é um administrador. O código abaixo ilustra algumas dessas possibilidades:
public class UsuarioLogado implements Serializable {
private Usuario usuario;
public void loga(Usuario usuario) {
this.usuario = usuario;
// poderia notificar outros sistemas sobre o login
}
public void desloga() {
this.usuario = null;
// poderia notificar outros sistemas sobre o logout
}
public boolean isLogado() {
return this.usuario != null;
}
public boolean isAdmin() {
return this.usuarios.pertenceAoGrupo("ADMIN");
}
}
O grande benefício de dar bons nomes que revelem sua verdadeira intenção é que ganhamos clareza no código e simplificamos a manutenção. Dessa forma, podemos aproveitá-los mesmo dentro das nossas páginas, como a seguir:
<h:form>
<!-- outras tags do formulario -->
<h:commandButton value="Solicitar" ... rendered="#{usuarioLogado.logado}" />
</h:form>
Ou em lógicas de negócio onde a verificação dos dados do usuário logado seja necessário, como abaixo:
@ApplicationScoped
public CancelamentoDeVendasService {
@Inject
private UsuarioLogado usuario;
@Transactional
public void cancela(Venda venda) {
if (!usuario.temPermissao("CANCELAR_VENDA"))
throw new UsuarioSemPermissoesException(usuario);
// lógica para cancelar venda
}
}
Um detalhe importante nessa abordagem é que o CDI nos garante sempre uma instância válida do objeto, dessa maneira para verificar se o usuário está logado basta injetar o objeto e invocar seu método isLogado()
, como no código abaixo:
public class FinalizaCompraBean {
@Inject
private UsuarioLogado usuario;
public String finaliza() {
if (!usuario.isLogado()) {
return "/login?faces-redirect=true";
}
// lógica para finalizar comprar
}
}
Neste caso, ter os dados preenchidos na instância de UsuarioLogado
significa que nosso usuário está de fato logado no sistema.
Concluindo
Existem inúmeras maneiras de modelar a segurança na aplicação, dependendo do seu framework MVC ou framework de segurança a implementação pode mudar um pouco. De qualquer forma, o importante é perceber que a idéia de usuário logado em muitos sistemas corporativos é mais aparente do que imaginamos, portanto ter uma representação bem definida do conceito e, conhecida por toda a equipe, ajuda bastante na hora de aplicar lógicas de autenticação e autorização dentro do código.
Não podemos esquecer que, modelar apropriadamente o usuário logado no sistema nos permite encapsular informações e lógicas de acesso, além de detalhes do framework de segurança. A abordagem nesse post é simples e prática, trazendo benefícios oriundos da orientação de objetos, como separação de responsabilidades e encapsulamento, e do seu framework de injeção de dependências, no nosso caso o CDI. Não é por acaso que essa abordagem é discutida e implementada no nosso curso de JSF 2, PrimeFaces e CDI.
E você, como tem feito para implementar a segurança no seu sistema?
You might also be interested in these articles...
Desenvolvedor e instrutor na TriadWorks