Controle de Login com VRaptor 3

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:

http://github.com/wbotelhos/controle-login-vraptor-3

  • Alexandre

    janeiro 15th, 2012

    Responder

    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

      Responder

      Obrigado Alexandre,

      Continue acompanhando que sempre tem novidades. (;

  • Roberto Fonseca

    dezembro 27th, 2011

    Responder

    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

      Responder

      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

    Responder

    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

      Responder

      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

    Responder

    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

      Responder

      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

    Responder

    como posso adicionar mais de uma classecontroller no accepts????

    • wbotelhos

      setembro 17th, 2011

      Responder

      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

        Responder

        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

  • Bruno Eustáquio

    março 30th, 2011

    Responder

    Muito obrigado Washington.

    Você entendeu perfeitamente minha dúvida. Vou analisar o vraptor-blank-project.

    Abraço.

  • Bruno Eustáquio

    março 30th, 2011

    Responder

    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

      Responder

      Oi Bruno,

      Fiquei um pouco confuso com sua dúvida.

      “…inserir todos os controlLers que são públicos no accepts…”

      O accepts dirá 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.

      “…ao inves de adicionar um por um no método…”

      Adicionar o .class de 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 != null será 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

    Responder

    Muito obrigado, Washington Botelho, fico muito agradecido. =)

  • Marcos

    janeiro 1st, 2011

    Responder

    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

      Responder

      Oi Marcos,

      Pode usar sim, até o código inteiro se quiser. Fique à vontade!
      Boa sorte na monografia! (:

  • Deckard Cain

    dezembro 30th, 2010

    Responder

    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

      Responder

      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

    Responder

    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

      Responder

      Fala Lucas,

      Você esta rodando o exemplo aqui do blog ou é uma aplicação real mesmo?
      O SessionUser é Component do tipo SessionScoped, 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

    Responder

    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 !

  • Maicon Pinto

    agosto 20th, 2010

    Responder

    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

      Responder

      Fala Maicon,

      Muito obrigado pelos parabéns.
      Espero estar ajudando a todos.

      Abraço. (:

  • Washington

    junho 16th, 2010

    Responder

    Perfeito!

    Pra mim a solução do Interceptor caiu como uma luva!

    Muito obrigado! []s

  • Washington

    junho 13th, 2010

    Responder

    corrigindo, apostila FJ-21 da caelum

  • Washington

    junho 13th, 2010

    Responder

    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

      Responder

      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

    Responder

    mais um excelente post do botelhos! [2]

    :)

  • Paulo Silveira

    abril 9th, 2010

    Responder

    mais um excelente post do botelhos!

  • Juninherman

    abril 8th, 2010

    Responder

    Fico bem simples de entender!! very good UOshintamm
    vlww abraço

Leave a Comment

* are Required fields