Resolviendo el control de acceso de usuarios utilizando HttpSession Tutorial Java J2ee

Por descargarpseint / hace 2 meses / 0 Comentarios ».

En el artículo inicial de esta serie de contenidos sobre el control de acceso, os introduje una solución planteada por Alejandro (un alumno mío) en la que pretendía controlar el acceso a un recurso web (página menu.jsp) utilizando un servlet que se encargase de validarlo contra unas credenciales de usuario válidas. Durante el mismo os expliqué algunos errores de diseño y de desconocimiento de los ámbitos de la aplicación (Request y Session) en los que había incurrido y os prometí que en el siguiente artículo os iba a plantear una solución óptima, a partir del código que el me había ofrecido. A continuación te presento esta solución. Disfrútala!!!

Construyendo una imagen clara del ciclo de navegación completo.

Lo primero que vamos a hacer es pensar en como esperamos que se comporte  la aplicación. Estamos hablando de controlar el acceso a recursos de una aplicación web (páginas jsp) utilizando un servlet como policía o responsable de dar o prohibir el paso. A mi me gustan las imágenes y a continuación os muestro una que describe gráficamente el comportamiento que le voy a exigir a la solución que pienso exponeros a continuación.

Descripción de los pasos:

  1. El visitante introduce su login y password y entonces pulsa en el botón Enviar.
  2. El servlet (vigilante) recibe esta solicitud y delega en el servicio de control de acceso quien será el responsable de dictaminar si el usuario puede pasar o no.
  3. El servicio de control de acceso, tras realizar las comprobaciones necesarias, devuelve el resultado de la validación al servlet de control de acceso.
  4. El servlet asume en este paso una función de controlador (patrón MVC) y redirige la solicitud a la vista adecuada (jspsi el usuario es válido, index.jsp si no lo es).

Mi apuesta ha sido modular el código y repartir las responsabilidades entre diferentes tipos de objetos  construyendo así una arquitectura sólida, y a la vez, escalable. A continuación paso a describirte cada uno de los protagonistas de la solución al control de acceso de usuarios.

Presentando un formulario de registro al usuario index.jsp

Para los mas puristas tengo que reconocer que la página index.jsp no suele ser el mejor candidato para contener el formulario de registro. Aquí he seguido utilizándola para no confundir mas a Alejandro y también porque muchas veces te puedes encontrar con código ya desarrollando que aplique prácticas no recomendables como ésta.

Su responsabilidad va a ser la de ofrecer un formulario de registro al visitante utilizando HTML puro. Este formulario lanzará una petición POST cuando el usuario introduzca los valores Usuario y Contraseña. Quiero también mencionar que en esta solución no estamos haciendo validaciones del lado del cliente utilizando javascript, sino que va a ser el propio servidor quien se encargue de realizar dichas comprobaciones.

Controlando el acceso a través de la clase ControlAccesoServlet.java

A continuación te presento el código del método doPost. Aquí está el corazón de la aplicación y por ello he delimitado muy bien sus responsabilidades y además he definido con exactitud la relación que mantiene con su clase delegada

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

String user = request.getParameter("user");
 String password = request.getParameter("password");
 HttpSession sesion = request.getSession();

boolean autenticado = controlAccesoService.estaUsuarioRegistrado(user, password, sesion);

if(autenticado){
 response.sendRedirect("privado/menu.jsp");
 }else{
 response.sendRedirect("index.jsp");
 }

}

Como habrás podido ver se trata de un código limpio y claro dividido en tres bloques de funcionalidad distinta:

  • Una primera parte de declaración de variables locales y también para recoger los parámetros de entrada.
  • Una segunda dondeejecutamos la lógica de control de acceso (invocando al método estaUsuarioRegistrado de java).
  • Finalmente un tercer bloque de código dondeel servlet se encarga de redirigir a la vista resultado adecuada.

Delegando la lógica de control de acceso en la clase de servicio ControlAccesoService.java

Aquí estoy definiendo una clase de servicio horizontal encargada de ofrecer una funcionalidad muy concreta al resto de la aplicación. Esta característica la vamos a poder explotar tanto en la solución que te estoy planteando hoy como en la próxima donde haré uso de un filtro web.

public boolean estaUsuarioRegistrado(String user, String password,
 HttpSession sesion) {
 boolean estaRegistrado = false;
 Usuario usuarioAValidar= new Usuario(user, password);

if(usuarioAValidar.tieneCredenciales())
 sesion.setAttribute("USUARIO_REGISTRADO", usuarioAValidar);
 else
 sesion.setAttribute("USUARIO_REGISTRADO", null);

estaRegistrado = sesion.getAttribute("USUARIO_REGISTRADO")!=null?true:false;

return estaRegistrado;
 }

El método estaUsuarioRegistrado con parámetros (String, String, HttpSession) tiene como responsabilidades:  comprobar que el usuario tiene credenciales y gestionar una variable de sesión que nos permita mantener el resultado de dicha validación durante varias peticiones.

Aquí quiero hacer un pequeño apunte respecto a uno de los problemas que destacaba dentro de la solución que vimos en el capítulo anterior. Utilizar la sesión del usuario para guardar las credenciales en lugar de variables locales o de ámbito de solicitud.

Volviendo al código que acabas de leer quiero comentarte que dos responsabilidades me parecen muchas para que sean asumidas por un mismo método. Aquí tenía varias opciones y la mas sencilla habría sido crear otro método privado auxiliar, dentro de la propia clase ControlAccesoService.java,  que se encargase de la comprobación de credenciales, sin embargo, opté por una segunda opción: responsabilizar al objeto Usuario de comprobar si tiene  o no credenciales para acceder a la parte privada de la aplicación.

Respecto al método que estamos analizando decidí que se quedara con la gestión de la variable de sesión que dejaría de ser una variable booleana (como vimos en el artículo anterior) para convertirse en un objeto de la clase Usuario. A continuación te explico porqué he optado por crear esta clase y también de que nos va a servir guardarla en sesión.

Aparición del concepto Usuario dentro de nuestro modelo de datos.

Lo primero que quiero explicarte es porque he decidido crear esta clase. No sería estrictamente necesario hacerlo, pero java es un lenguaje orientado a objetos y uno de sus principios es conseguir representar con objetos todos aquellos conceptos del problema que estamos resolviendo que pensemos que puedan ser relevantes. Y dentro del control de acceso de usuarios, el concepto usuario si parece tener la suficiente importancia.

Una segunda razón (mucho mas discutible si vemos Usuario desde la perspectiva de los objetos POJO) sería la de hacerle responsable de comprobar si las credenciales del objeto que encapsula son válidas o no. Habría sido mas correcto, y posiblemente te ofrezca esta solución en las próximas versiones del control de acceso, implementar otra clase de servicio (UsuarioService.java) que se encargara de ofrecer servicios horizontales asociados al usuario. Lo podrás ver en el próximo artículo.

Una vez realizadas estas dos aclaraciones te expongo el código de esta clase
public class Usuario {

private String user;
private String password;

public Usuario(String user, String password) {
this.user = user;
this.password = password;
}

private boolean usuarioNulo(){
if(user==null || password==null)
return true;
else
return false;
}

public boolean tieneCredenciales() {
String usuario = “s”;
String contraseña =”s”;
boolean tieneCredenciales = false;

if(!usuarioNulo() && usuario.equals(user) && contraseña.equals(password))
tieneCredenciales= true;

return tieneCredenciales;

}

}

Si nos fijamos en la implementación de la clase podrás ver una primera parte de declaración de atributos del usuario (user y password) y una segunda con dos métodos con lógica de negocio.

¿Cuál es la función de tieneCredenciales()?

Se trata de un método que encapsula tanto la validación del usuario (comprobación de que los datos introducidos son correctos, es decir, no nulos) como de validarlo contra unas credenciales internas del sistema (la forma de definir estas credenciales proviene de la propuesta inicial de Alejandro. En una aplicación realista, esta información se extraería de Base de Datos, LDAP o Certificado Electrónico, por citar tres formas de hacerlo.

La responsabilidad de validación de usuario la he extraído a un método auxiliar llamado usuarioNulo(), encargándose tieneCredenciales de extraer las credenciales y validar los datos introducidos por el visitante contra ellas.

usuarioNulo() se encarga de comprobar si alguno de los atributos del usuario son nulos, en cuyo caso, directamente finaliza el proceso de validación de acceso.

Hasta aquí tenemos resuelto el caso de uso donde un visitante (no registrado) intenta acceder a la aplicación desde la URL raíz (http://localhost/ControlAccesoApp). Sin embargo, nos queda una segunda parte donde el visitante (estando registrado o no) pudiera saltarse el flujo de navegación normal y, por ejemplo, intentase entrar directamente al recurso privado introduciendo la siguiente URL (http://localhost:8080/ControlAccesoApp/privado/menu.jsp)

Utilizando el control de acceso en la vista menu.jsp

El contenido ofrecido por esta pantalla es un privilegio al que solo tendrán acceso aquellos usuarios que estén registrados. Es un requisito muy habitual y el origen del problema que me planteó Alejandro.

Nosotros ya sabemos que en sesión vamos a tener un objeto Usuario que almacenará las credenciales del usuario que está actualmente registrado en la sesión. ¿Es esto suficiente para garantizar un control de acceso efectivo?.

La respuesta es que no, ya que, además de saber que un usuario pasó el proceso de validación previamente es necesario comprobar que quien intenta acceder a menu.jsp es justo quien se validó en peticiones anteriores. Recuerdo aquí que el protocolo HTTP no tiene estado y por ello tenemos que implementar mecanismos adicionales para suplir dicha carencia o, mejor dicho, característica de este protocolo de comunicación.

El código es el siguiente

<%@ page import="com.programarenjava.negocio.ControlAccesoService" %>
 

<% ControlAccesoService controlAcceso = new ControlAccesoService(); boolean usuarioRegistrado = controlAcceso.estaUsuarioRegistrado(request.getSession()); if(!usuarioRegistrado) response.sendRedirect("../index.jsp"); %>

Si nos fijamos en el código de arriba podrás apreciar un código bien estructurado donde, primero declaramos e instanciamos el servicio de control de acceso, después comprobamos si el usuario está validado y finalmente si no lo está lo redirigimos a la vista que le muestra el formulario de registro.

El método de contrloAcceso que estamos llamando es estaUsuarioRegistrado con un parámetro (HttpSession), es decir, que lo estamos sobrecargando para que pueda adartarse a este segundo escenario. A continuación te explico que es lo que es lo que hace

public boolean estaUsuarioRegistrado(HttpSession sesion){

Usuario usuarioValidado = (Usuario) sesion.getAttribute(“USUARIO_REGISTRADO”);
boolean estaRegistrado = usuarioValidado!=null?true:false;
return estaRegistrado;
}

Como puedes ver es un método diferente al caso anterior (cuando lo invocábamos con 3 parámetros). ¿Por qué lo he diseñado así?. Nuevamente para simplificar el código resultado y responsabilizar a cada método de un tipo de validación diferente.

En este escenario, el usuario no va a introducir sus credencias sino que directamente intentará acceder a la vista /privado/menu.jsp. Por ello no tiene sentido que el objeto Usuario intente validar su login, password frente a las credenciales válidas. Por ello, con que el usuario exista en la sesión es suficiente para validar que éste está registrado correctamente.

El funcionamiento del mecanismo de control de acceso sería el siguiente:

  1. El usuario intenta acceder al recurso privado (jsp).
  2. Esta vista invoca al servicio de control de acceso (controlAcceso)
  3. Éste recupera de la sesión el usuario registrado.
  4. Si el usuario no se registró previamente entonces el objeto devuelto será nulo y el servicio de control de acceso devolverá falso.
  5. Si el usuario si se registró previamente entonces el objeto devuelto será distinto de nulo ycontrolAcceso devolverá cierto.
  6. La vista (jsp) redirigirá al visitante aindex.jsp en el caso 5 y le mostrará su contenido cuando estemos en el caso 6.

Antes de dar por terminada la solución quiero ofrecerte un extra relacionado con los test unitarios. Mientras estábamos programando esta solución (me encanta que mis alumnos programen mientras los superviso. Así aprenden de verdad enfrentándose a una pantalla en blanco y no partiendo siempre de la solución para después intentar entenderla) tuvimos un comportamiento inesperado.

Aproveché la ocasión para explicarle a Alejandro los beneficios de los test unitarios y de JUnit (además forma parte del temario del que tiene que examinarse) y le propuse que desarrollásemos una prueba que validara el correcto funcionamiento del método tieneCredenciales de la clase Usuario.

Aquí te dejo el código
@Test
public void elUsuarioSSTieneCredenciales() {
String user=”s”;
String password=”s”;
boolean resultado = false;

Usuario usuarioAValidar =new Usuario(user, password);
resultado =usuarioAValidar.tieneCredenciales();

assertTrue(resultado);
}

@Test
public void elUsuarioJJNoTieneCredenciales(){
String user=”j”;
String password=”j”;
boolean resultado=true;

Usuario usuarioAValidar = new Usuario(user, password);
resultado = usuarioAValidar.tieneCredenciales();

assertFalse(resultado);
}

En el próximo capítulo te explicaré como utilizar los filtros web. Primero te plantearé un cambio que te ayudará a entender la principal desventaja de esta solución. ¿Ya sabes a que me estoy refiriendo?.

Si no es así no te preocupes, pronto lo veremos  y entonces seguro que entiendes el porqué de estos elementos  (los filtros) y cuales son sus principales bondades.

Para explicar esta solución para el control de acceso de usuarios he necesitado introducir algunos conceptos (web y java) que quizás no comprendas bien y aunque he intentado buscar y dejar enlaces que puedan ayudarte en su comprensión, quiero comentarte que estoy aquí para ayudarte y para completar este artículo con nueva información (de mi cosecha) pensada para responder a tus dudas. Además, no expliqué en detalle el test unitario porque es un tema que ya hemos abordado en esta web y que puedes encontrar dentro del Manual en Java Programando una calculadora paso a paso.  Sin embargo, necesito de tu ayuda y tu participación para poder detectar esos temas que hayan podido quedar mas confusos. Anímate y participa o escríbeme!!!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *