Autenticacion en JSF

En este artículo se explica como restringir el acceso a ciertas partes de nuestra aplicación mediante la implementación de un mecanismo de autenticacion en JSF utilizando un formulario de login de usuario. Un usuario registrado podra acceder a las páginas restringidas al autenticarse ingresando sus credenciales en el formulario que se muestra a tal efecto.

Paso 1: Crear la página de login para autenticacion en JSF

Esta es la página que contiene el formulario de login con los campos para el ingreso de nombre de usuario y contraseña y el botón que invoca la lógica de validación del usuario. Se hará uso de un filtro para que esta página pueda ser accedida por usuarios anónimos (no autenticados en la aplicación). Si un usuario autenticado intenta ingresar a esta página, el filtro lo redirigirá a una página privada por defecto.

<div class="field">
	<h:inputText id="username" value="#{loginBean.nombreUsuario}" p:placeholder="Nombre de Usuario" required="true" requiredMessage="Ingrese Nombre de Usuario." />
</div>
<div class="field">
	<h:inputSecret id="contrasena" value="#{loginBean.contrasena}" onkeyup="passwordValueChanged(this);" p:placeholder="Contraseña" required="true" requiredMessage="Ingrese Contraseña." />
	<h:graphicImage id="show" library="images" name="show.jpg" onmouseover="changeCursor(this, 'pointer');" onmouseout="changeCursor(this, 'default');" onclick="managePasswordVisibility(true);" />
	<h:graphicImage id="hide" library="images" name="hide.jpg" style="display:none;" onmouseover="changeCursor(this, 'pointer');" onmouseout="changeCursor(this, 'default');" onclick="managePasswordVisibility(false);" />
</div>
<h:commandButton id="loginbutton" value="Login" action="#{loginBean.login}" />

Paso 2: Crear un recurso protegido

Nuestro recurso protegido sera una página que solo podrá ser accedida por un usuario autenticado en la aplicación. Si un usuario anónimo intenta acceder a este recurso ingresando directamente la URL en el navegador el filtro se encargará de redirirlo a la página de login. Esta vista cuenta con un commandLink que invoca a la lógica que se encarga de cerrar la sesión.

<h:form>
	<div style="border-bottom: 1px solid #CCC;padding:10px;">
		<h:panelGrid columns="2" columnClasses="col-1, col-2" style="width:100%;">
			<h:outputText value="Bienvenido: #{user.firstName} #{user.lastName}." />
			<h:commandLink action="#{loginBean.logoff}" value="Salir" />
		</h:panelGrid>
	</div>
</h:form>

Paso 3: Crear las reglas de navegación

Se crearán las reglas de navegación para los flujos normales por los que podrá moverse el usuario dentro de la aplicación. Esto no incluye excepciones, como las redirecciones por acceso no autorizado.

Un caso de navegación es por ejemplo un usuario que visualiza la página de login, realiza el proceso de autenticacion JSF. Una vez autenticado, la aplicación lo lleva a la página de bienvenida.

Otro caso es un usuario autenticado que hace click en el link de logoff. Una vez cerrada la sesión la aplicación lo llevará a la página de login. La definición de las reglas de navegación se realizan en el archivo faces-config.xml.

<faces-config>
	<navigation-rule>
		<from-view-id>/pages/welcome.xhtml</from-view-id>
		<navigation-case>
			<from-action>#{loginBean.logoff}</from-action>
			<from-outcome>success</from-outcome>
			<to-view-id>/pages/login.xhtml</to-view-id>
		</navigation-case>
	</navigation-rule>
	<navigation-rule>
		<from-view-id>/pages/login.xhtml</from-view-id>
		<navigation-case>
			<from-action>#{loginBean.login}</from-action>
			<from-outcome>success</from-outcome>
			<to-view-id>/pages/welcome.xhtml</to-view-id>
		</navigation-case>
	</navigation-rule>
</faces-config>

Paso 4: Crear el Managed Bean

El Managed Bean es el que recibe los datos de las credenciales del usuario para realizar la validación de las mismas. Esta validación se hace en el método login() como sigue:

  • Se consultan los datos del usuario por nombre de usuario invocando un servicio destinado a tal efecto. A los fines de este tutorial el servicio no accede a una base de datos (cosa que debe hacerse en una aplicación real). El mismo contiene los datos de usuario hardcodeados.
  • Una vez obtenidos los datos del usuario (encapsulados en un objeto User) se verifica si la contraseña es correcta.
  • Si la contraseña es correcta se guarda el objeto User en sesión y se redirige al usuario a la página de bienvenida retornando ‘success’ que es el outcome de la regla de navegación de login con destino a la página de bienvenida (welcome.xhtml).
  • Si la contraseña es incorrecta se muestra un mensaje al usuario y se retorna un String vacío con lo cual no se realizará ninguna acción de navegación.

En el proceso de logoff se elimina el objeto User de sesión y se redirige al usuario a la página de login retornando ‘success’ que es el outcome de la regla de navegación de logoff con destino a la página de login (login.xhtml).

@Named("loginBean")
@RequestScoped
public class LoginBean {

	@Inject
	private LoginService service;
	
	private String nombreUsuario;
	private String contrasena;

	public String getNombreUsuario() {
		return nombreUsuario;
	}
	
	public void setNombreUsuario(String nombreUsuario) {
		this.nombreUsuario = nombreUsuario;
	}
	
	public String getContrasena() {
		return contrasena;
	}
	
	public void setContrasena(String contrasena) {
		this.contrasena = contrasena;
	}
	
	public String login() {
		System.out.println("LoginBean.login()");
		User user = this.service.getUser(this.nombreUsuario);

		if (user != null && user.getPassword().equals(this.contrasena)) {
			HttpSession session = (HttpSession)FacesContext.getCurrentInstance().getExternalContext().getSession(true);
			session.setAttribute("user", user);
			return "success";
		} else {
			FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_WARN, "Credenciales no válidas", "Credenciales no válidas");			
			FacesContext.getCurrentInstance().addMessage(null, msg);
			return "";
		}
	}

	public String logoff() {
		System.out.println("LoginBean.logoff()");
		HttpSession session = (HttpSession)FacesContext.getCurrentInstance().getExternalContext().getSession(true);
		session.removeAttribute("user");
		return "success";
	}

}

Paso 5: Crear un filtro para restringir accesos

En Java utilizamos Filtros para interceptar las peticiones entrantes para realizar algún procesamiento antes de que estas peticiones lleguen al Servlet de la aplicación. En nuestro caso lo utilizaremos para dar acceso a las páginas de nuestra aplicación sólo si el usuario está autenticado.

Para crear un Filtro debemos implementar la interfaz javax.servlet.Filter. La lógica necesaria se implementará en el método doFilter(ServletRequest, ServletResponse, FilterChain).

public class LoginFilter implements Filter {
	public static final String LOGIN_PAGE = "/login.jsf";		
	public static final String WELCOME_PAGE = "/welcome.jsf";
	
	@Override
	public void init(FilterConfig arg0) throws ServletException {
		
	}
	
	@Override
	public void destroy() {
		
	}

	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
			throws IOException, ServletException {
		System.out.println("LoginFilter.doFilter()");
		
		HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
		HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;		
		User user = (User)httpServletRequest.getSession().getAttribute("user");		
		
		if (user != null) {
			if (httpServletRequest.getRequestURI().endsWith(LOGIN_PAGE)) {
				httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/pages" + WELCOME_PAGE);
			} else {
				filterChain.doFilter(servletRequest, servletResponse);
			}
		} else {
			if (!httpServletRequest.getRequestURI().endsWith(LOGIN_PAGE) && 
					!httpServletRequest.getRequestURI().contains("show.jpg") && 
					!httpServletRequest.getRequestURI().contains("hide.jpg") &&
					!httpServletRequest.getRequestURI().contains("styles.css") &&
					!httpServletRequest.getRequestURI().contains("scripts.js")) {
				httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/pages" + LOGIN_PAGE);
			} else {
				filterChain.doFilter(servletRequest, servletResponse);
			}
		}
	}
}

Finalmente, debemos configurar el Filtro en el archivo web.xml.

<filter>
	<filter-name>login-filter</filter-name>
	<filter-class>com.jcodepoint.filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>login-filter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>
Autenticacion. Página de login.

Conclusión

Implementar la autenticación en las aplicaciones JSF es un paso importante para mejorar su seguridad y facilidad de uso. Mediante el uso de un filtro Java, los desarrolladores pueden restringir fácilmente el acceso a partes específicas de la aplicación.


Te puede interesar:

Facelets y uso de templates en JSF

Facelets es un lenguaje de declaración de vistas para JavaServer Faces, que permite crear vistas usando templates HTML.

Seguir leyendo →

Arquitectura y Ciclo de Vida JSF

Cómo funciona el ciclo de vida de una aplicación JSF. En el artículo relacionado se cubre el manejo de eventos.

Seguir leyendo →