Login y control de acceso básico con PrimeFaces (Paso a paso)

 Hola Gente,

vamos a ver como hacer un formulario de login y un control de acceso utilizando PrimeFaces.
No usaremos bases de batos ni lógicas complejas, solo se trata de que un usuario no pueda acceder a ningún recurso sino no está logueado en el sistema.

Nos basamos en el post anterior "Primeros pasos con PrimeFaces, Eclipse y Tomcat (Paso a paso)", ya no entraré en tanto detalle de como realizar ciertas tareas que se encuentran explicadas en el post anterior.

Creando la vista

Debemos crear un archivo en "WebContent", o sea en el root de nuestra aplicación, llamado "login.xhtml"

El contenido es el siguiente:


<html xmlns="http://www.w3c.org/1999/xhtml"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:f="http://java.sun.com/jsf/core"
       xmlns:p="http://primefaces.org/ui">
<h:head> </h:head> <h:body style="text-align:center">
    <p:growl id="mensajes" showDetail="true" life="2000" />
   <h:form>
      <p:panel header="Login" style="width:300px">
        <h:panelGrid columns="2" cellpadding="5">
          <h:outputLabel for="username" value="Usuario:" />
          <p:inputText value="#{loginBean.nombre}" id="username"
             required="true" label="username" />
          <h:outputLabel for="password" value="Clave:" />
        <p:password value="#{loginBean.clave}" id="password" required="true"
           label="password" />
        <f:facet name="footer">
          <p:commandButton id="loginButton" value="Login"
             actionListener="#{loginBean.login}" update=":mensajes"
             oncomplete="manejarLogin(xhr, status, args)" />
        </f:facet>
      </h:panelGrid>
    </p:panel>
  </h:form>
</h:body>
<script type="text/javascript">
  //<![CDATA[
  function manejarLogin(xhr, status, args) {
    if (!args.validationFailed && args.estaLogeado) {
      setTimeout(function() {
        window.location = args.view;
      }, 500);
    }
  }
//]]>
</script>
</html>

En un navegador, este código produce la siguiente vista:

Analicemos ahora un poco el código:

<p:growl id="mensajes" showDetail="true" life="2000" />

Se utiliza para mostrar mensajes emergentes, los generaremos desde loginBean, por ejemplo:

<p:inputText value="#{loginBean.nombre}" id="username" 
   required="true" label="username" />

Este tag representa el campo que contiene el nombre del usuario que una vez que sea transmitido (submit) establecerá el valor en un bean, loginBean, del lado del servidor mediante el llamado al método setNombre() por el atributo nombre. Es un campo obligatorio, esto es, no puede estar en blanco.

<p:password value="#{loginBean.clave}" id="password" required="true"  label="password" />

Este tag anterior trabaja similar al de nombre pero con la clave, además el campo no mostrará lo que se tipea.

Por último:

<p:commandButton id="loginButton" value="Login"
        actionListener="#{loginBean.login}" update=":mensajes"
      oncomplete="manejarLogin(xhr, status, args)" />

Es el botón que envía los campos al server (nombre y clave) y ejecuta el método login() de loginBean, una vez que se ejecuta al método mencionado se actualiza el componente de mensajes emergentes para mostrar los mensajes que se hayan acumulado, además ejecuta el script cliente manejarLogin(xhr, status, args). Las líneas más importantes del método son:

if (!args.validationFailed && args.estaLogeado) {

Nos aseguramos que el proceso de validación no haya fallado y que el usuario esté logueado. Si el usuario está logueado correctamente se ejecuta:

window.location = args.view;
donde args, es una selección de parámetros que recibiremos del servidor, en este caso nos envía el parámetro viewcon el nombre de la vista que debe ser cargada. A esto lo hacemos con un retardo para que se puedan mostrar los mensajes emergentes de bienvenida.

Creando el componente del servidor

Ya hemos visto como se realiza esta operación, debemos crear una clase java llamadaar.com.magm.web.primefaces.LoginBean, que debe ser luego agregada al faces-config.xml como bean de sesión, el código que para hacerlo es:

<managed-bean>
   <managed-bean-name>loginBean</managed-bean-name>
   <managed-bean-class>ar.com.magm.web.primefaces.LoginBean</managed-bean-class>
   <managed-bean-scope>session</managed-bean-scope> </managed-bean>

Para poder copiar el código anterior, es necesario seleccionar la pestaña "Source" del editor del archivo de configuración de faces.

El código de la clase recién creada es el siguiente:
 

 package ar.com.magm.web.primefaces;
import java.io.Serializable;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.servlet.http.HttpSession;
import org.primefaces.context.RequestContext;
public class LoginBean implements Serializable {
private static final long serialVersionUID = -2152389656664659476L;
private String nombre;
private String clave;
private boolean logeado = false;
public boolean estaLogeado() {
return logeado;
} public String getNombre() {
return nombre;
} public void setNombre(String nombre) {
this.nombre = nombre;
} public String getClave() {
return clave; }
public void setClave(String clave) {
this.clave = clave;
}
public void login(ActionEvent actionEvent) {
RequestContext context = RequestContext.getCurrentInstance();
FacesMessage msg = null;if (nombre != null && nombre.equals("admin") && clave != null&& clave.equals("admin")) {logeado = true;msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Bienvenid@", nombre);} else {logeado = false;msg = new FacesMessage(FacesMessage.SEVERITY_WARN, "Login Error","Credenciales no válidas");}FacesContext.getCurrentInstance().addMessage(null, msg);context.addCallbackParam("estaLogeado", logeado);if (logeado)context.addCallbackParam("view", "gauge.xhtml");} public void logout() {HttpSession session = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(false);session.invalidate();logeado = false;} }

Además de los gettes y setters para nombre y clave y el getter para saber si el usuario está logueado, tenemos dos métodos más: login() y logout(), analicemos login().

 

 if (nombre != null && nombre.equals("admin") && clave != null
     && clave.equals("admin")) {
     logeado = true;
      msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Bienvenid@", nombre);

Si el nombre de usuario y contraseña son correctos y además poseen el valor "admin", consideramos que el usuario es válido y lo logueamos ejecutando:
 

logeado = true;

Luego establecemos el mansaje de bienvenida del que hablamos antes:

msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Bienvenid@", nombre);

Aclaración: si alguno se está preguntando porque el único usuario es "admin" y la clave es "admin" y ambos valores están "hardcodeados" y no se pueden cambiar, la respuesta es que he simplificado esa tarea para no hacer más complejo el ejemplo, además no resulta muy complejo obtenerlo de una base de datos por ejemplo.

 

Si el usuario no está logueado:
 

    } else {
      logeado = false;
      msg = new FacesMessage(FacesMessage.SEVERITY_WARN, "Login Error",
                             "Credenciales no válidas");
    }

Simplemente lo aseguramos estableciendo el valor de la variable logeado en falso y estableciendo un mensaje de advertencia al usuario.

Finalmente enviamos el mensaje que establecimos antes:

FacesContext.getCurrentInstance().addMessage(null, msg);
 

Agregamos un parámetro que será utilizado en el script del cliente:

context.addCallbackParam("estaLogeado", logeado);

Si el usuario ha podido loguearse sin inconvenientes, le enviamos como parámetro la próxima vista.

if (logeado)   context.addCallbackParam("view", "gauge.xhtml");

El método logout() que aún no hemos utilizado realiza lo siguiente:

Obtiene la sesión del usuario:

HttpSession session = (HttpSession) FacesContext.getCurrentInstance()                                     .getExternalContext().getSession(false);

La invalida:

session.invalidate();

y desloguea al usuario:

logeado = false;

Agregando la opción logout a "gauge.xhtml"

Editamos "gauge.xhtml" y agregamos el siguiente código inmediatamente después del body:

<h:body><p:commandLink id="logout" actionListener="#{loginBean.logout}" style="margin-right:20px;" oncomplete="logout(xhr, status, args)"><h:outputText value="logout" /></p:commandLink>

Se agregará un link como el que se ve en la siguiente figura:

El link ejecutará el método loginBean.logout y una vez completado ejecutará el script del cliente logout(....)

El script del cliente debe agregarse entre el cierre del tag body y el cierre del tag html como sigue:

</h:body> <script type="text/javascript">   //<![CDATA[   function logout(xhr, status, args) {     setTimeout(function() {       window.location = 'login.xhtml';     }, 500);   } //]]> </script> </html>

 
Como se puede observar solo hacemos un redirect a la página de login.

Controlando el acceso a todos los recursos

Hasta aquí hemos creado los componentes necesarios para permitir al usuario presentar sus credenciales y que sean comprobadas y almacenadas en una sesión, también permitimos al usuario terminar su sesión, pero aún falta algo muy importante ya que hasta aquí debemos confiar en que todos los usuarios son buenos y pasan primero por el proceso de login antes de peticionar cualquier otro recurso. En fin, definitivamente no podemos confiar en esto, debemos crear un mecanismo tal que si un usuario o proceso requiere un recurso sin que se hayan validado sus credenciales no pueda acceder al mismo. A esto lo hacemos con el uso de un filtro que es un componente que se ejecuta antes de que se maneje la petición final en el server y puede "torcer" el flujo de ejecución normal si es necesario.

Creando el filtro

Para crear el filtro presionamos Ctrl+N en la vista "Project Explorer" y seleccionamos "Filter" de la categoría "Web" y presionamos el botón "Next >"

Completamos la primer pantalla con:

 Java package: ar.com.magm.web.filters
Class name: LoginFilter

Presionamos "Next >"

Luego cambiamos el URL Pattern por "/*" para que este filtro se ejecute ante cualquier petición.


y presionamos "Finish"

El código que debe tener el método doFilter(..) se encuentra a continuación y está explicado con comentarios en el mismo código:
 

public void doFilter(ServletRequest request, ServletResponse response,
 FilterChain chain) throws IOException, ServletException {
  HttpServletRequest req = (HttpServletRequest) request;
  HttpServletResponse res = (HttpServletResponse) response;
   // Obtengo el bean que representa el usuario desde el scope sesión
  LoginBean loginBean = (LoginBean) req.getSession().getAttribute("loginBean");    //Proceso la URL que está requiriendo el cliente  String urlStr = req.getRequestURL().toString().toLowerCase();  boolean noProteger = noProteger(urlStr);  System.out.println(urlStr + " - desprotegido=[" + noProteger + "]");    //Si no requiere protección continúo normalmente.  if (noProteger(urlStr)) {    chain.doFilter(request, response);    return;  }    //El usuario no está logueado  if (loginBean == null || !loginBean.estaLogeado()) {    res.sendRedirect(req.getContextPath() + "/login.xhtml");    return;  } 
  //El recurso requiere protección, pero el usuario ya está logueado.
  chain.doFilter(request, response);
}

He separado la lógica de excepción de control de recursos en un método muy sencillo que se encuentra a continuación y que también forma parte del filtro el cual no requiere mucha explicación.

private boolean noProteger(String urlStr) {

/*
 * Este es un buen lugar para colocar y programar todos los patrones que
 * creamos convenientes para determinar cuales de los recursos no
 * requieren protección. Sin duda que habría que crear un mecanizmo tal
 * que se obtengan de un archivo de configuración o algo que no requiera
 * compilación.
 */
  if (urlStr.endsWith("login.xhtml"))
    return true;
  if (urlStr.indexOf("/javax.faces.resource/") != -1)
    return true;
  return false;
}

Este filtro completa este pequeño sistema de control de acceso y logueo, espero que les sea útil.

El workspace completo de este post, del anterior y de futuros posts que tengan que ver con el tema se encuentra en github en la siguiente dirección: https://github.com/magm3333/workspace-pftuto

Saludos
 
Mariano

 

Hola Mariano, ante todo te felicito por este post, explicas muy brevemente algo que puede ser todo lo complicado que uno quiera. Tengo un duda en la configuracion del web.xml, el problema es que ya creado el filtro, cuando trato de acceder a la web de inicio, no me la carga, no me da error pero no la carga, mi proyecto se llama Login, y el path donde esta la web inicial es suelto dentro de WebContent. No se como especificar bien la ruta para que cargue la web de inicio, lo he intentado de varias maneras cambiando la ruta dentro del metodo "noProteger" pero nada. Saludos y ojala puedas ayudarme. Eduardo

 Hola, 

te recomiendo imprimir la salida del método getRequestURL(), en una de esas no sirve endWith() y tienes que usar indexOf().

Utiliza las herramientas de debug para seguir paso a paso el intento de acceso, esto no tiene ninguna ciencia, dberías darte cuenta del problema con un buen seguimiento del flujo de ejecución.

Saludos

Mariano

Hola, seguí tu ejemplo, pero me sucede exactamente lo que a Mariano, me carga el login.xhtml, pero no se ve nada en la pagina, agregue el loginTemplate.xhtml por si era problema que no cargaba. No funciona, algún consejo? Saludos.

En respuesta a por LuisNeiraN (no verificado)

Para que no te salga la pagina en balnco debes poner la linea de codigo a si: res.sendRedirect(req.getContextPath() + "/faces/login.xhtml");

Estimado,


con nada de información es imposible recomendarte algo salvo que si eres creyente reces a ver si funciona.

Hablando en serio pasa un poco más de info como para poder sacar conclusiones, ya que si haz escrito el código tal cual está en el post y has seguido todas las instrucciones, debe funcionar perfecto.

Por otro lado, si eres desarrollador, deberías usar alguna herramienta de debug para seguir paso a paso la ejecición y ver dónde está el problema.


Saludos

 

Mariano

Hola que tal, eh revisado mi codigo mil veces, pero no entiendo por que no se está haciendo el redirect hacia el gauge.xhtml. Si bien entiendo este pedazo de codigo if (logeado) context.addCallbackParam("view", "gauge.xhtml"); Es el indicado de mandar a ejecutar el redirect hacia gauge.xhtml. Eh debugueado mi código y si se está seteando el parametro view. Alguna idea?

Saludos,

Primero que nada muchas gracias por compartir este exelente post, lo he probado y funciona perfectamente solo quisiera ver si me pueden ayudar con una duda. La cuestion es que mi forma para login la tengo en un popup en mi pagina index.xhtml. En mi filtro tengo definido que proteja todo mi sitio excepto mi pagina index.xhtml. Mi pagina index tiene un template del cual hereda el header y footer, pero cuando cargo esta pagina index no me los muestra asumo que es por que esas paginas estan bloquedas por el filtro. Asi que no se si me ayudan con una pequenia idea de como deproteguer varias paginas al mismo tiempo en un filtro.

Gracias y sigan adelante.

Como puedes ver, el filtro es muy precario, yo recomiendo utilizar otras técnicas, pero si quieres salir rápido andando puedes modificarlo para que soporte tu excepción, por ejemplo:
if (urlStr.endsWith("login.xhtml" || expresión)

donde expresión será lo que tu definas que haga que no se filtre tu template. Igualmente te recomiendo utilizar expresiones regulares, sería algo como:
if (Pattern.compile(tuExprReg).matcher(urlStr).find())
return true;

Donde deberás escribir tu expresión regular en tuExprReg, de esta forma es más potente y flexible.
Saludos
Mariano

Hola, muy buenos tus posteos sobre primefaces... es mas, lo poco que se lo aprendi de ti...
trabajo en una obra social, tengo que dieñar una aplicacion web para controlar los Planes Especiales (medicamentos) que se les da a los afiliados y sus consumos...
quiero tener una pagina principal y con un menu cambiar solo el contenido de un p:layoutUnit ubicado en el centro..
tienes algun ejemplo, antes solo lo hacia con jquery cambiando el contenido del div
div.load('pagina.html')
como lo hace con primefaces o facelets...
gracias

En respuesta a por juan matias (no verificado)

Hola Juan,
la verdad es que lo poco que se de este tema es lo que expuse ya que no es mi trabajo diario, esto lo publiqué ya que tuve que investigarlo para dictar una capacitación dentro de la cual tocábamos este tema de forma secundaria. No debe ser nada complejo ya que no me llevó mucho investigar para producir el material. Decidí publicarlo para que rindan un poco más las horas invertidas.
Si llegas a alguna solución, te pido que enriquezcas el material con tu aporte para que nos sirva a todo@s.
Saludos
Mariano

alguien me puede explicar por que cuando regrese de una pagina me lleva  a login en vez de quedarse en la pagina principal del proyecto

 

Gracias por su apoyo

 

Saludos

Hola, probé el código y funciona bien pero de igual manera puedo ingresar a la pagina gauge.xhtml sin necesidad de loguearme mediante la barra de direcciones... pasa que cuando te logueas y desloqueas igual puedes entrar con la barra de direcciones y el filtro no actúa sino que permite ingresar o sea no redirecciona....

Saludos Mariano El metodo de logout no esta funcionando. Pudieras darnos alguna idea de como hacerlo funcionar??