Atualizado em 17 de Setembro de 2011.
Vamos abordar um assunto que todo desenvolvedor em algum momento irá precisar: controle de login. Hoje em dia é normal você acessar um sistema e lá estar uma telinha para ser digitado o nome de usuário e senha.
Por tempos achei complicado trabalhar com login em uma aplicação JSP / Servlet pura, ou criar filtros e mais filtros, mas estou pra dizer que se você já passou por isso, irá ficar satisfeito com o VRaptor e seus Interceptadores. (:
Objetivo:
Criar uma tela de autenticação e uma página de boas vindas disponível apenas para usuários logados.
Precisamos de ter nossa JPA configurada para acesso ao banco de dados. Se você ainda não sabe como fazer isso, da uma passadinha no artigo JPA e VRaptor 3.
Criando a classe Usuario:
@Entity
public class Usuario {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nome;
private String email;
private String senha;
// getters e setters
}
Dissemos que a classe Usuario é uma entidade que deverá ser mapeada no banco e o atributo id será a chave primária com o seu incremento feito pelo próprio banco de dados. Iremos utilizar o email como identificação do usuário junto com a sua respectiva senha.
Criando a classe UsuarioBusiness:
private EntityManager manager;
public UsuarioBusiness(EntityManager manager) {
this.manager = manager;
}
public Usuario autenticar(String email, String senha) {
try {
Query query = manager.createQuery("from Usuario where email = :email and senha = :senha");
query.setParameter("email", email);
query.setParameter("senha", senha);
return (Usuario) query.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
Fizemos uma consulta procurando um registro que tenha um e-mail e senha igual ao digitado pelo usuário. Se for encontrado alguma ocorrência, este usuário é retornado, caso contrário um valor nulo será.
Vamos criar uma classe com escopo de sessão para mantermos o nosso usuário.
Criando a classe UserSession:
@Component
@SessionScoped
public class UserSession {
private Usuario user;
public boolean isLogged() {
return user != null;
}
public void logout() {
user = null;
}
// get e set
}
Este componente servirá para manter o usuário na sessão, por isso ele deve ser anotado com @SessionScoped e como de costume o @Component por também se tratar de um componente. Criamos um método que verifica se o usuário esta na sessão, ou seja, é diferente de nulo e outro método para retirar o usuário da sessão, que neste caso é o logout.
Agora precisamos identificar os métodos que são protegidos por login e os que são públicos. Por isso iremos criar uma annotation para indicar os método públicos chamada @Public.
Não se assuste se nunca tiver criado uma annotation em sua vida, muitos desenvolvedores, mesmos os mais experiêntes ainda não tiveram esta oportunidade.
Criando a anotação pública (Public.java):
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface Public {
}
Para criarmos uma anotação utilizamos o modificador @interface. Além disso anotamos esta classe com uma @Retention para especificar qual será a política de retenção. Neste caso utilizaremos a RetentionPolicy.RUNTIME, que nos disponibiliza as informações em tempo de execução. Outra anotação importante para utilizarmos é a @Target, que configuramos onde esta anotação de permissão poderá ser utilizada, que em nosso caso será em classes (TYPE) e métodos (METHOD).
Para aprender um pouco mais sobre annotation você pode ler o artigo “Trabalhando com Annotations em Java” do nosso amigo Francisco Souza. (:
Com isso podemos utilizar anotações em classes e métodos de forma simples para indicar que tais recursos poderão ser utilizados sem que o usuário esta logado.
Precisamos de uma página para o usuário digitar o e-mail e senha.
Criando a página de Login (login.jsp):
<form action="${pageContext.request.contextPath}/autenticar" method="post">
E-mail: <input type="text" name="usuario.email"/>
Senha: <input type="password" name="usuario.senha"/>
<input type="submit" value="Acessar"/>
</form>
Temos um formulário que submete o e-mail e senha para um método do LoginController que por sua vez fará a autenticação.
Criando a classe LoginController:
@Resource
public class LoginController {
private Result result;
private UserSession userSession;
private UsuarioBusiness business;
public LoginController(Result result, UserSession userSession, UsuarioBusiness business) {
this.result = result;
this.userSession = userSession;
this.business = business;
}
@Public
@Get("/login")
public void login() {
}
@Public
@Post("/autenticar")
public void autenticar(Usuario usuario) {
Usuario user = business.autenticar(usuario.getEmail(), usuario.getSenha());
if (user != null) {
userSession.setUser(user);
result.redirectTo(IndexController.class).index();
} else {
result.include("error", "E-mail ou senha incorreta!").redirectTo(this).login();
}
}
@Get("/logout")
public void logout() {
userSession.logout();
result.redirectTo(this).login();
}
}
Nosso controller tem três métodos:
- O @Get /login apenas nos leva para a página de autenticação;
- O @Post /autenticar é o método que recebe o e-mail e senha do formulário e faz a consulta no banco. Se o usuário existir, então este é colocado na sessão e redirecionado para dentro do sistema; e
- O /logout desloga o usuário colocando um valor nulo na sessão e o redireciona para a página de login.
Repare que o método que leva à página de login e o método para autenticação estão anotados com @Public, isso evitará que o sistema faça o bloqueio do recurso, já que o usuário necessita dessas ações para se autenticar. Já o método logout, apenas quem esta logado poderá ter acesso a ele.
Criando o IndexController:
@Resource
public class IndexController {
@Get("/")
public void index() {
}
}
Temos apenas um método para nos redirecionar para a página inicial interna do sistema. Sendo que este controller não possibilita acesso público.
Criando a página inicial (index.jsp):
Nossa página inicial terá apenas uma mensagem de boas vindas ao usuário logado que estará na sessão e um link para ser desfeito o login (logout).
...
Olá, ${userSession.user.nome} <a href="${pageContext.request.contextPath}/logout">Logout</a>
...
Com isso terminamos a parte um pouco mais trabalhosa que já estamos acostumados a fazer, sobrando a cereja do bolo, a qual será de fato o controle e restrição de acesso ao sistema.
Todo esse controle será feito por uma classe chamada de Interceptor, na qual irá interceptar todas ações efetuadas no sistema fazendo uma auditoria que de acordo com sua regra tomará uma atitude.
Criaremos esta classe que estenderá Interceptor e receberá injetado o UserSession para nos disponibilizar o usuário da sessão.
Esta classe deve ser anotada com @Intercepts para conseguir interceptar as chamadas.
Criando a classe LoginInterceptor:
@Intercepts
public class LoginInterceptor implements Interceptor {
private Result result;
private UserSession userSession;
public LoginInterceptor(Result result, UserSession userSession) {
this.result = result;
this.userSession = userSession;
}
public boolean accepts(ResourceMethod method) {
return
!(method.getMethod().isAnnotationPresent(Public.class) ||
method.getResource().getType().isAnnotationPresent(Public.class));
}
public void intercept(InterceptorStack stack, ResourceMethod method, Object resourceInstance) {
if (userSession.isLogged()) {
stack.next(method, resourceInstance);
} else {
result.redirectTo(LoginController.class).login();
}
}
}
Todo interceptor tem um método accepts no qual podemos fazer regras para indicar se o recurso acesso será ou não interceptado. Nós já pensamos em uma forma de excluir alguns recursos do filtro do interceptor através da anotação @Public. Logo verificamos se o método ou o controller esta anotado com ela, se estiver será retornado true, então negamos o resultado para false dizendo: “Não aceitamos a interceptação desta ação!”. Aqui pode entrar qualquer regra que lhe convenha.
Caso o recurso seja interceptado, o método intercept irá ser executado para o tratamento. Este método recebe como argumento um InterceptorStack que será responsável por dar continuidade ao fluxo da execução através do método next. Há também outros dois argumentos que serão discutidos em outro artigo e não são necessárias por hora. Repare que capturamos a sessão e verificamos se o usuário esta logado, através do método que criarmos no componente, dando continuidade ao fluxo, caso contrário é feito um redirecionamento para a página de login para que o usuário se autentique.
Ok! Nosso controle de login esta pronto, e cá pra nós: se você souber um jeito mais fácil de fazer isso em Java, por favor, me mostre como é que eu te pago um saquinho de jujubas. (;
Link do projeto:
Alexandre
janeiro 15th, 2012
Muito bom o post cara, me ajudou muito. E de quebra, aprendi a fazer annotations.
Parabéns pelo blog. Ta show!!!
wbotelhos
março 23rd, 2012
Obrigado Alexandre,
Continue acompanhando que sempre tem novidades. (;
Roberto Fonseca
dezembro 27th, 2011
Olá Botelhos!
Cara foi aqui que sempre lia quando iria fazer algo, hoje, depois de tanto tempo não preciso mais vir a este post, entretanto nessa madrugada pensando longeeeeeee em um sistema, gostaria de saber,
1)como faria para não deixar o mesmo usuário fazer login em máquinas diferentes…. por um determinado tempo. Ou seja, o login tem que estar em sessão e na aplicação…como proceder?
2) o tempo de sessão é determinado no web.xml, correto?
3) como determino o tempo da aplicação?
Obrigado!
wbotelhos
março 23rd, 2012
Oi Roberto,
Não tinha visto a sua pergunta, acho que alguma coisa no WordPress bugou. =/
1. Eu ainda não fiz esse tipo de verificação e até me perguntaram um tempo atrás sobre isso. Talvez algum componente no nível de ApplicationScoped ou mesmo manipular algum valor no banco equanto o cara estiver logado. Já tem alguma solução legal?
2. É sim, mas pode ser feito também na aplicação.
3. Não entendi o que quis dizer com o tempo da aplição.
RODRIGOPORTO
setembro 25th, 2011
Olá Washington, gostaria de saber como eu instalo o vraptor3… posta pra mim um tutorial ou manda os links pra eu baixar aqui, obrigado…
wbotelhos
setembro 25th, 2011
Oi Rodrigo,
O VRaptor é uma framework Java para desenvolvimento de softwares, então não há instalação.
Você precisa de ter instalado em sua máquina a JDK do Java, um banco de dados como o MySQL, por exemplo e uma IDE de desenvolvimento como o Eclipse.
Ai então poderá iniciar o desenvolvimento de suas aplicações.
Para iniciar recomendo estes artigo Iniciando Com VRaptor 3.
solano
setembro 17th, 2011
Muito obrigado pela ajuda!
Uma outra pergunta, vc tem material sobre webservice com javaee. se possível encaminhe um email virgilio.solano@hotmail.com
Aguardo.
wbotelhos
setembro 17th, 2011
Oi Solano,
Eu sua homenagem atualizei o post. (:
Não tenho, quando preciso assim dou uma pesquisada rapidinha mesmo. :P
Abraço.
solano
setembro 17th, 2011
como posso adicionar mais de uma classecontroller no accepts????
wbotelhos
setembro 17th, 2011
Oi Solano,
Só você adicionar mas controllers separando-os por vírgula.
Vou dar uma atualizada neste artigo, pois hoje em dia utilizo uma annotation @Public para marcar os métodos ou controllers que não quero interceptar.
solano
setembro 17th, 2011
Meu caso é o seguinte, tenho a classe de clientecontroller que possui o metodo login e possuo também outros controllers como por exemplos, o homeController que possui os metodos das paginas do site, este controller esta sendo barrado no meu filtro, somente o login que esta passando, preciso que todos os metodos da classe homecontroller passem no filtro.
Como vc disse que utiliza a anotacao @Public, quero saber como posso utilizar no meu? é necessário deixar também alguma classe no accepts?
neste post estao algumas duvidas e ocorrências dos erros
http://www.guj.com.br/java/252753-problemas-com-logout/2#1314574
wbotelhos
setembro 17th, 2011
Oi Solano,
Só adicionar o controller separado por vírgula:
A outra estratégia você pode conferir no VRaptor Starting Project: http://github.com/wbotelhos/vraptor-starting-project
Bruno Eustáquio
março 30th, 2011
Muito obrigado Washington.
Você entendeu perfeitamente minha dúvida. Vou analisar o vraptor-blank-project.
Abraço.
Bruno Eustáquio
março 30th, 2011
Botelhos,
Primeiramente, parabéns pelos posts do blog, estou começando a utilizar o VRaptor e os posts estão sendo muito úteis.
Queria tirar uma dúvida. No caso de uma aplicação que possua um front end público e um backend protegido pelo login, existe uma forma mais fácil de inserir todos os controlers que são públicos no accepts do interceptor ao inves de adicionar um por um no método? Talvez por módulo sei lá. rsrs
Abraço.
wbotelhos
março 30th, 2011
Oi Bruno,
Fiquei um pouco confuso com sua dúvida.
O
acceptsdirá se será aceito a interceptação, mas com o negado no início irá deixar de interceptar. Você pode tirar o negado para adicionar ali quem você quer interceptar.Adicionar o
.classde cada controller que você quer excluir no método do accepts? Se sim, faça o que te falei invertendo o resultado. Se tiver mais para excluir, então passa quem você quer interceptar e vice-versa.Se você tem poucas páginas para interceptar com login, te dou a dica de abandonar o interceptor de login e usar apenas um de permissão, sendo que o
user != nullserá uma regra a mais para acessar tal área.No Blank Project que disponibilizei esta usando essa lógica: https://github.com/wbotelhos/vraptor-blank-project
Marcos
janeiro 1st, 2011
Muito obrigado, Washington Botelho, fico muito agradecido. =)
Marcos
janeiro 1st, 2011
Olá amigo, gostei do exemplo, aliás, posso usar partes desse código na minha monografia? Eu vou alterá-lo é claro pra se adequar a minha necessidade.
Att,
Marcos Lauande
wbotelhos
janeiro 1st, 2011
Oi Marcos,
Pode usar sim, até o código inteiro se quiser. Fique à vontade!
Boa sorte na monografia! (:
Deckard Cain
dezembro 30th, 2010
Obrigado pelo belo post!
Estou mudando de struts2 para vraptor e só nesse post pude ver como tudo é literalmente elegante, simples e lógico.
O layout do blog também é muito bom
wbotelhos
dezembro 30th, 2010
Fala Deckard,
Ótima decisão. Tenho certeza que ficará satisfeito com as facilidades e a capacidade do VRaptor.
Só acompanhar os feeds que sempre tem novidade.
Abraço. (:
Luca Bastos
novembro 24th, 2010
Washington
Muito bom seu post.
Gostei tanto que usei sua idéia no projeto em que estou. Só que tenho um problema que não sei se aconteceu contigo.
Quando o sistema sobe, não existe SessionUser. O Tomcat mostra uma exception e segue em frente. Mas o JBoss empaca com uma exception do Spring:
Error creating bean with name ‘loginInterceptor’ defined in file LoginInterceptor.class:
Unsatisfied dependency expressed through constructor argument with index 2 of type [SessionUser]
Você passou por algo semelhante?
wbotelhos
novembro 26th, 2010
Fala Lucas,
Você esta rodando o exemplo aqui do blog ou é uma aplicação real mesmo?
O
SessionUseréComponentdo tipoSessionScoped, então ele só exisitirá em algum lugar se você injetá-lo.Você poderá injetar de três formas:
- Injeção pelo construtor;
- Injeção usando o
@Autowired;- Ou mapeando no
url-mapping.xml.Você verificou se foi injetado direitinho o
SessionUser?juniorsatanas
novembro 16th, 2010
Gostaria de ver esse Demo : controle de usuário com permissão de acesso tudo junto ! Ese BLOG aqui é melhor que curso !
PARABÉNS !
Cara bom da porra !
wbotelhos
novembro 16th, 2010
Oi Junior,
Se você ler o artigo artigo Controle de Login e Controle de Permissão já consiguirá implementar as duas features.
Pra melhorar disponibilizei o código do Interceptor do Movie Collection no Gist do Github: https://gist.github.com/662999
Bons estudos e obrigado pelo elogio. (:
Maicon Pinto
agosto 20th, 2010
Eu vi este post algum tempo atrás, no teu blog do wordpress, e hoj dando uma olhada nos meus twitts eu acabei encontrando o link, e sinceramente, hoje é muito mais simples de ver todo teu exemplo.
:P Naquela época eu não tinha noção de nada, e hoje achei bem interessante e abre para muitas idéias.
Passando mesmo só para dar os parabéns pelo blog ( mais uma vez ) e desejar tudo de bom!
wbotelhos
agosto 21st, 2010
Fala Maicon,
Muito obrigado pelos parabéns.
Espero estar ajudando a todos.
Abraço. (:
Washington
junho 16th, 2010
Perfeito!
Pra mim a solução do Interceptor caiu como uma luva!
Muito obrigado! []s
Washington
junho 13th, 2010
corrigindo, apostila FJ-21 da caelum
Washington
junho 13th, 2010
Gostei bastante do post…já tinha começado algo semelhante pela apostila FJ-11, mas achei muito interessante aqui o UsuarioDao.
Porém não entendi muito bem o return do accepts: return !Arrays.asList(LoginController.class).contains(method.getMethod().getDeclaringClass());
Mas o mais importante é que após fazer o logout eu consigo navegar pra dentro da aplicação com o botão voltar do navegador…tem alguma dica de como resolver isso? Qualquer coisa que me ajude.
Obrigado…[]s
wbotelhos
junho 16th, 2010
Fala @Washington,
O retorno do accepts diz se os método será (true) ou não (false) interceptado.
Então ele pega a lista de métodos de um controller e pergunta se o método que você esta acessando esta contido nessa lista. Se estiver contido retornará true, porém seu negado (false) faz com que o método não seja interceptado.
Quanto a voltar a página anterior, isso ocorre devido o chace que o navegador faz.
Você pode criar um NoCacheInteceptor para evitar que o navegador guarde cache, assim isso não ocorrerá mais.
No cookbook da Caelum tem este interceptor já pronto:
http://vraptor.caelum.com.br/cookbook/evitando-que-o-browser-faca-cache-das-paginas
Francisco Souza
abril 20th, 2010
mais um excelente post do botelhos! [2]
:)
Paulo Silveira
abril 9th, 2010
mais um excelente post do botelhos!
Juninherman
abril 8th, 2010
Fico bem simples de entender!! very good UOshintamm
vlww abraço