Pesquisar

segunda-feira, 3 de novembro de 2008

Login - autenticação e autorização usando JavaServer Faces (jsf)

Este post trará (ou tentará trazer) informações básicas para o desenvolvimento de um sistema de login para aplicações web javaserver faces (jsf) . Com estas informações creio que será possível pra você implementar seu sistema de Login.
Basicamente...

O Usuario tem um nomeDeUsuario uma senha e um ou mais Perfis.
Cada Perfil está vinculado a uma ou mais páginas. (ou recursos ou qualquer nomenclatura que você quiser usar)
Uma Página é um objeto simples que tem uma descrição.
Na tentativa de resumir a estrutura seria algo semelhante a essa pseudo-modelagem...
Pagina paginaAdmin = new Pagina("/admin.jsf");
Pagina paginaNormal= new Pagina("/principal.jsf");

Perfil admin = new Perfil();
admin.temAcesso(paginaAdmin).temAcesso(paginaNormal);

Perfil normal = new Perfil();
normal.temAcesso(paginaNormal);

Usuario homerSimpsons = new Usuario("homer99","senha");
homerSimpsons.temPerfilDe(admin);
Crie um PhaseListener como abaixo.
package meu.pacote.br;

import javax.faces.application.NavigationHandler;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.servlet.http.HttpSession;

public class AuthorizationListener implements PhaseListener {

public void afterPhase(PhaseEvent event) {
FacesContext facesContext = event.getFacesContext();
//adiquirindo o FacesContext.
String currentPage = facesContext.getViewRoot().getViewId();
//armazenando a página que fez a requisição. (a string da pág. atual ex: "/pag.jsf")
boolean isLoginPage = (currentPage.lastIndexOf("login.xhtml") > -1);
//fazendo a verificação mais básica de todas... se é a página de login.
HttpSession session = (HttpSession) facesContext.getExternalContext().getSession(true);
//adquirindo a sessão (essa mesma onde você deverá guardar seu usuário no nível de sessão com descritor currentUser).
Usuario user = (Usuario) session.getAttribute("currentUser");
//apenas recuperando o valor da sessão.
if (!isLoginPage && user == null) {
NavigationHandler nh = facesContext.getApplication().getNavigationHandler();
nh.handleNavigation(facesContext, null, "loginPage");
//bem, se não está logado redireciona pra lógica que (navigatio rule) atende a loginPage
} else {
//verificar se o usuario atual tem acesso a página atual.
boolean temAcesso = user.temAcesso(new Pagina(currentPage));
if (!temAcesso){
//aqui a logica de não ter acesso... redicione novamente? faça algo... ???
}
}
//caso contrário o jsf passa tranquilamente por aqui!!!

}

public void beforePhase(PhaseEvent event) {
//poderia ter sido escrito nesse evento antes da "fase" (lembra do básico do jsf, o ciclo de vida e as fases...
}

public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
}

E uma lógica pra logar o usuário na sessão...
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("currentUser", user);
E registre essa classe no arquivo faces-config.xml. Claro, registre também um navigation que atenda a qualquer chamada para loginPage.

<lifecycle>
<phase-listener>
org.gpro.filter.AuthorizationListener
</phase-listener>
</lifecycle>

<navigation-rule>
<from-view-id>/*</from-view-id>
<navigation-case>
<from-outcome>loginPage</from-outcome>
<to-view-id>/login.xhtml</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>

Claro que o código pode e deve ser modificado para questões de flexibilidade, legibilidade e até mesmo performance. O propósito inicial era mostrar como desenvolver um sistema de login com jsf. O código mostrado foi feito apenas pra exemplificar qualquer erro ou dúvida postem aqui.
Mais informações em:

15 comentários:

Victor disse...

Era o que eu procurava. Muito bom.

Leandro disse...

Espero que funcione... caso não volte e poste o motivo (execeção) do não funcionamento...

V1P3r disse...

bom dia. eu estou tentando fazer essa implementação no meu código, mas eu percebi que vc coloca o else. Eu entendi, mas estou com dúvidas na linha:
boolean temAcesso = user.temAcesso(new Pagina(currentPage));
Esse new Pagina, eu tenho que criar essa classe?? onde eu instancio esse objeto??
Agradeço o post e os esclarecimentos.

V1P3r disse...

Outra coisa que esqueci de perguntar: onde eu vou colocar isso??

FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("currentUser",user);

Valeu.

Leandro disse...

"Esse new Pagina, eu tenho que criar essa classe?? onde eu instancio esse objeto??"
A Classe Pagina é uma VO (Martin Fowler) que fiz pra representar um recurso que é acessado... você a instância em qualquer local, seria prudente faze-la ser imutavel.

Leandro disse...

FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("currentUser", user);

Esse código você colocará onde você guarda o usuário (normalmente provindo do banco de dados) na sessão do servidor. (Session...)

Qualquer dúvida estou aqui.

V1P3r disse...

Olá Leandro. Primeiramente, quero te agradecer a atenção por ter me respondido. Sou novato no Java e estou tentando implementar um filtro de segurança para autenticação do usuário, de forma que ele não possa acessar as jsp's via url. Eu li as suas respostas, mas fiquei com dúvida na criação da classe Pagina. Teria como você postar esse exemplo da classe Pagina??
Valeu meu amigo. Agradeço novamente.

Leandro disse...

@Entity
public class Pagina {
@Id
private String url;
//getters and setters omitidos.

public Pagina(){}
public Pagina(String url){
this.url = url;
}
}

Estava pensando em postar o exemplo completo... depois.

Goiaba disse...

Leandro, estou com uma dúvida e agradeço se puder me ajudar.
Minha intenção é fazer com que o usuário só tenha acesso a determinadas páginas depois de logado. No caso de não estar logado e tentar acessar tais páginas, ele deve ser redirecionado para a página principal (onde poderá fazer o login). Minha dúvida: Por exemplo, estou na tela de login (index.xhtml), que pode ser acessada por todos, e clico num link que me leva para uma página que só pode ser acessada após a autenticação (aposAut.xhtml). O de currentPage deve ser index.xhtml ou aposAut.xhtml? O que recebo é index.xhtml. É como se a url estivesse uma página atrasada. Espero ter sido claro. Obrigado.

Leandro disse...

"O de currentPage deve ser index.xhtml ou aposAut.xhtml?"

Quando está noutra página recebe o aposAut.xhtml... se está recebendo o index.xhtml deve ser porque está sendo redirecionado.

Não tive esse problema, acho que vou reescrever esse tutorial com mais detalhes em breve está havendo muitas dúvidas.

V1P3r disse...

Olá Leandro, td bem?
Ainda estou quebrando cabeça com o PhaseListener, mas aos poucos estou entendo o funcionamento. Não tenho muito tempo pra me dedicar a esse estudo, mas td bem... Olha só: eu fiz a implementação e ela está funcionando. Realmente, está bloqueando o aceso via URL. O problema é que após eu acessar a pagina principal através do login, qualquer outra requisição retorna a tela de login novamente. Pelo que eu estou percebendo, é algum problema com o ciclo de vida da sessão, não é isso??
O que eu posso fazer pra resolver?? Agradeço.

Leandro disse...

"O problema é que após eu acessar a pagina principal através do login, qualquer outra requisição retorna a tela de login novamente. Pelo que eu estou percebendo, é algum problema com o ciclo de vida da sessão, não é isso??"

Seguinte, posta seu código do PhaseListener e também seu navigation-rule (faces-config.xml)! Eu acho que o problema pode estar aí...

V1P3r disse...

Segue meu PhaseListener:
========================================
public class AuthorizationListener implements PhaseListener {

private static final long serialVersionUID = 1L;

public void afterPhase(PhaseEvent event) {
System.out.println("Imprime fase after: " + event.getPhaseId());

FacesContext facesContext = event.getFacesContext();
String currentPage = facesContext.getViewRoot().getViewId();
System.out.println("Imprime pagina atual: " + currentPage);

boolean isLoginPage = (currentPage.lastIndexOf("login.jsp") > -1);
HttpSession session = (HttpSession) facesContext.getExternalContext()
.getSession(true);
Login login = (Login) session.getAttribute("currentUser");

if (!isLoginPage && login == null) {
System.out.println("<-------usuario nao logado-------->");
NavigationHandler nh = facesContext.getApplication()
.getNavigationHandler();
nh.handleNavigation(facesContext, null, "loginPage");
} else {
}
}

public void beforePhase(PhaseEvent event) {

}

public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}

}
=======================================
Meu faces-config.xml:
========================================
tive de retirar as <.
faces-config>
navigation-case>
from-outcome>loginPage /from-outcome>
to-view-id>/login.jsp /to-view-id>
/navigation-case>
/navigation-rule>


lifecycle>
phase-listener>br.gov.rr.setrabes.util.AuthorizationListener /phase-listener>
/lifecycle>

/faces-config>
=======================================

Leandro disse...

Acredito que o erro está aqui:

if (!isLoginPage && login == null) {
NavigationHandler nh = facesContext.getApplication()
.getNavigationHandler();
nh.handleNavigation(facesContext, null, "loginPage");
} else {
}
}

debugue o código ou veja com sysout o valor de isLoginPage e login.

Acho que nesse ponto as páginas estão sendo todas redirecionadas.
Imprima os valores e me diga o que aparece:

System.out.println(currentPage);
System.out.println(isLoginPage);
System.out.println(login);

ps: insira esse código antes do if...

Anônimo disse...

Acabei de adicionar seu feed para meus favoritos. Eu realmente gosto de ler seus posts.