jueves, febrero 14, 2013

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 view con 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 llamada ar.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

Tutorial anterior: http://jmagm.blogspot.com/2013/02/primeros-pasos-con-primefaces-eclipse-y.html
Próximo tutorial: http://jmagm.blogspot.com/2013/02/una-tabla-con-datos-de-una-base-de.html

Saludos

Mariano

No hay comentarios.:

Etiquetas

pentaho (45) java (35) eclipse (23) jdbc (14) curso (13) tomcat (13) primefaces (12) db2 (11) mondrian (10) review (10) jsf (9) openI (9) pdi (9) prd (9) libro (8) plugin (8) musql (7) struts (7) javascript (6) spring (6) cdf (5) ctools (5) instalar (5) linux (5) mysql (5) data studio (4) hibernate (4) ireport (4) jasper (4) meteor (4) videocurso (4) eglu (3) eglubi (3) elearning (3) graphite (3) grupo eglu (3) jboos tools (3) mexico (3) openbits (3) packt (3) python (3) undec (3) websphere (3) applet (2) cde (2) dao (2) db2university (2) exelearning (2) flexigrid (2) hadoop (2) iua (2) kettle (2) moodle (2) node (2) olap (2) osbi (2) pivot4j (2) scorm (2) sql (2) stpivot (2) actionscript (1) amazon (1) autenticacion (1) avanzado (1) base de datos (1) big data (1) bigdata (1) bodoc (1) cambiar (1) ccc (1) cdc (1) chat (1) cloud (1) coffeescript (1) control de acceso (1) corti (1) csv (1) cuba (1) curso meteor undec (1) dashboard (1) datamart (1) dataptix.tv (1) datasource (1) datatable (1) db2 ExpressC (1) demonio (1) distancia (1) driver (1) driver jdbc (1) eglufiltertwolist (1) encapsulamiento (1) especialización (1) etl (1) excepciones (1) export (1) faces (1) federación (1) filas afectadas (1) filtertwolist (1) filtrado (1) flegrid (1) flex (1) google (1) google viz (1) hostname (1) html (1) i18n (1) ibm (1) identidad (1) indignación (1) instancias (1) inteligencia de negocios (1) jee (1) jpivot (1) l10n (1) la azada (1) la zaga de los confines (1) layout (1) liberado (1) libre (1) libro promoción (1) lob (1) marktplace (1) menu (1) meteor node javascript google oauth autenticacion (1) mobile (1) mongoDB (1) node.js (1) oauth (1) olap4j (1) open source (1) orm (1) persistencia (1) personalizada (1) prd5 (1) psw (1) publicidad (1) rad6 (1) recursividad (1) reporting (1) rock (1) saiku (1) script (1) servicio (1) sessiontimeout (1) sourceforge (1) spinneta (1) sqlserver (1) ssl (1) taller (1) troyanx (1) ubuntu (1) ucc (1) ui (1) web (1) web console (1) xampp (1) xml (1) xpath (1)

Seguidores