Mostrando las entradas con la etiqueta doFilter. Mostrar todas las entradas
Mostrando las entradas con la etiqueta doFilter. Mostrar todas las entradas

domingo, 2 de junio de 2013

Filtros de Tomcat - Parte II

En el post anterior (Filtros de Tomcat - Parte I) expliqué los fundamentos de los filtros en Tomcat. En este segunda parte mostraré un ejemplo práctico de cómo utilizar un filtro.

El código fuente de referencia está disponible aquí.

1. Ejemplo Práctico: Filtro de Autenticación
El objetivo del filtro es asegurar que las páginas sólo estén disponibles para usuarios que han superado exitosamente el proceso de autenticación. Sin un filtro, este requerimiento implica la intervención de todas las páginas .jsp de la aplicación (asumamos por un momento que no se está trabajando con ningún framework específico que ya provea esta funcionalidad).

Para efectos del ejemplo, consideraremos un sitio web muy simple, en adelante "minisitio" (como dicen los abogados), que tiene una página con contenido (index.jsp) y una página de ayuda (ayuda.jsp). La página index.jsp está restringida para los usuarios autenticados correctamente. La página ayuda.jsp no requiere usuarios autenticados para acceder a ella. El flujo típico de autenticación de usuarios es el que se muestra en el siguiente diagrama. Las páginas del lado izquierdo requieren usuarios autenticados. Las páginas del lado derecho no.
2. Autenticación
Hay múltiples maneras de resolver la autenticación de usuarios. Para efectos de este ejemplo, la autenticación exitosa será registrada utilizando las variables de sesión del contexto. En términos simples, si un usuario se autenticó correctamente, se marcará la variable "logged" de la sessión con el valor "true".

Considerando el diagrama anterior, el flujo es como sigue:
  1. Usuario ingresa a la página login.jsp, ingresa su usuario y password y pulsa el botón Ingresar.
  2. Los datos son enviados a la página checkLogin.jsp que valida si el usuario es válido contra algún sistema (base de datos, archivo, etc.)
  3. Si no es válido, reenvía al usuario al paso 1 con un mensaje descriptivo del error. 
  4. Si es válido, se registra el valor "true" en la variable "logged" de la sesión. 
  5. El usuario puede acceder a la página de contenido index.jsp
  6. Cuando el usuario quiere salir del sistema, es enviado a la página logout.jsp que invalida la sesión.
  7. El usuario es redirigido a la página login.jsp
3. Validación sin Filtro
Sin el uso de un filtro, la validación de los usuarios autenticados en las páginas restringidas implicaría agregar el código para validar la información de la sesión en cada página. Si bien es cierto esto se puede hacer reutilizando código por medio de includes, funciones, bibliotecas, etc., no es una estrategia que permita crecer en el tiempo. Un ejemplo simple de este enfoque es que la responsabilidad de agregar la validación queda en manos del desarrollador. Si el desarrollador olvida incorporar el código que valida la sesión, la página quedará expuesta a usuarios no autenticados.

4. Validación con Filtro
Veamos ahora cómo implementar un filtro para validar el acceso a las páginas restringidas del minisitio descrito anteriormente.

4.1 Configuración del Filtro
Considerando el flujo de autenticación descrito previamente (algo así como los requerimientos), hay algunas páginas que deben tener autenticación y otras que no. La página ayuda.jsp es, por definición, una página que no requiere usuarios autenticados, sin embargo, las páginas login.jsp, checkLogin.jsp y logout.jsp tampoco requieren usuarios autenticados. No tiene mucho sentido exigir un usuario autenticado para la página de login por ejemplo. Lo mismo sucede con las otras. Por otra parte, cuando el filtro detecte el acceso de un usuario no autenticado a una página restringida, debe saber a qué página debe enviar al usuario para que se autentique. En el caso del minisitio, la página es login.jsp, sin embargo, podría ser necesario modificar esta página dinámicamente.

Para cumplir con lo anterior, se considera la definición de dos parámetros (exclude y loginPage) en la configuración del filtro para permitir modificar este comportamiento dinámicamente. La configuración, será como sigue en el archivo web.xml.

  <filter>
    <filter-name>LoginFilter</filter-name>
    <filter-class>cl.abertini.filters.LoginFilter</filter-class>
    <init-param>
        <param-name>exclude</param-name>
        <param-value>/login.jsp ... /ayuda.jsp</param-value>
    </init-param>
    <init-param>
        <param-name>loginPage</param-name>
        <param-value>/login.jsp</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>LoginFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>


4.2 Construcción del Filtro

Como se mencionó en la primera parte, el filtro se construye heredando de la clase javax.servlet.Filter y se deben implementar los métodos siguientes init(...), doFilter(...) y destroy(). A continuación se describe la implementación realizada en el contexto del ejemplo.
  • init(FilterConfig filterConfig)

    El método, básicamente, lee la configuración del archivo web.xml y rescata la información de los parámetros exclude (páginas que no requieren usuarios autenticados) y loginPage (la página que permite a un usuario autenticarse).

    La configuración se almacena en las variables sExclude y sLoginPage de la clase para ser utilizadas en el método doFilter(...).
  • doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

    Este es el método principal y que será ejecutado cada vez que se acceda a una página del contexto.

    En términos simples, primero se valida que la página a la que se está accediendo no sea una página excluida. En caso de serlo, se pasa el control al siguiente filtro de la cadena (secuencia) por medio de la instrucción chain.doFilter( request, response ).

    En caso de no ser una página excluida, se valida si el atributo "logged" está en la sessión. Para efectos del ejemplo, sólo se valida que exista y no necesariamente que tenga el valor "true".

    Si el atributo no existe, se redirige la respuesta a la página configurada como loginPage. Notar que en este caso, no se traspasa el procesamiento a ningún filtro de la cadena una vez que se reenvió al usuario a la página indicada.

    En caso contrario, es decir, que el atributo "logged" existe, simplemente se traspasa el control al siguiente filtro como se explicó anteriormente.
  • destroy()

    En este ejemplo, dado que la información que administra el filtro (parámetros exclude y loginPage) no consumen muchos recursos, no se realiza nada. Si el filtro que se esté implementado, requiriera administrar grandes cantidades de memoria y/o estructuras de datos complejas y/o cachés o similares, entonces, en este método se debieran implementar los procedimientos para asegurar una correcta destrucción (y almacenamiento tal vez) de la información.
5. Pruebas
Para probar el código anterior, se requiere tener instalado Tomcat 6.X ó superior. Se debe habilitar el contexto en el directorio webapps y levantar el servidor. Una vez levantado, se debiera poder ver el sitio en la siguiente URL:

http://localhost:8080/testSite/login.jsp

Para probar que el filtro está operativo, se puede intentar acceder a la página restringida, con la siguiente URL:

http://localhost:8080/testSite/index.jsp

Si es que no se ha realizado el proceso de autenticación, el filtro debiera bloquear el acceso y reenviar el browser a la página de login. Por el contrario, si se accede a la página de ayuda, no debiera haber problemas ya que está definida como una página sin acceso restringido. Para acceder a esta página, se debe utilizar la siguiente URL:

http://localhost:8080/testSite/ayuda.jsp

Para probar la autenticación, se debe usar la palabra "prueba" como usuario y password en la página de login. Si se ingresan los datos correctos, se debiera poder acceder a la página index.jsp sin problemas.

6. Más Filtros y Extensiones
La distribución estándar de Tomcat 7 incluye varios filtros. En términos generales, los filtros provistos proveen funcionalidades destinadas a mejorar la seguridad del sitio (XSS, CSRF), algunos para depuración y otros para manipular aspectos del HTML entregado al cliente (encoding por ejemplo).

En la siguiente página de Oracle hay una muy buena descripción de la infraestructura de filtros y algunos ejemplos interesantes.

The Essentials of Filters

De todos los ejemplos que he visto al respecto, el siguiente me parece el mejor para demostrar las capacidades de los filtros. El filtro descrito permite reducir el tamaño del HTML entregado al cliente eliminando algunas secuencias como espacios, líneas en blanco, etc. Es interesante porque muestra un truco para poder "capturar" la respuesta (HTML) que va a ser entregada al cliente, como parte del flujo de salida de la cadena (secuencia) de filtros (ver punto 3.2 de la primera parte). Una extensión de este mecanismo sería la construcción de un modelo simple de templates.

Minifying HTML in the Servlet container

7. Conclusiones
La próxima vez que se vaya a implementar una funcionalidad transversal a un sitio, es recomendable analizar si aplica al construcción de un filtro o no. En esta consideración, más allá de las cosas operativas y las precauciones que se pudieran tomar como parte del proceso de desarrollo, la más importante es la posibilidad de asegurar que ciertas funcionalidades se apliquen sin necesidad de depender de la "buena voluntad" de los desarrolladores. Esto, a la larga, no sólo simplifica el proceso si no que, además, asegura que los desarrolladores se focalicen en el desarrollo de las páginas y sus objetivos. Las condiciones higiénicas asociadas ya van a estar implementadas.

sábado, 25 de mayo de 2013

Filtros de Tomcat - Parte I

Supongamos por un momento que es necesario cortar un árbol y se poseen las siguientes herramientas: un Hacha y un Serrucho.

¿Cuál herramienta es la más eficiente?

La mayoría de las personas considerarán el serrucho como la más eficiente. ¿Por qué? Básicamente porque todos conocemos las capacidades de un serrucho y podemos compararla con el hacha. Supongamos ahora que no conocemos ninguna de las herramientas y debemos elegir. La elección no es tan simple porque no conocemos las capacidades de cada una. El sentido común no nos permitiría hacer una elección correcta tampoco.

Lo anterior es un ejemplo muy simple de lo importante que es conocer (y tener) las herramientas adecuadas para realizar una tarea.

Hace algunas semanas atrás, un cliente solicitó una modificación a uno de los productos de la empresa en la que trabajo. La modificación implicaba modificar todas las páginas .jsp de la aplicación y, obviamente, había un plazo que cumplir, por lo que, la intervención manual de todas las páginas no era una opción. Había que buscar otra alternativa. ¿La solución? Filtros de Tomcat.  

1. Filtros de Tomcat
El servidor web Apache Tomcat provee una infraestructura denominada Filtros que permite interceptar el flujo de entrada y salida desde y hacia una página (.jsp) e intervenirlo. La gracia es que el filtro se programa una vez y permite realizar la tarea en todas las páginas existentes en el contexto.

La infraestructura de filtros está disponible desde la Servlet API 2.3 y fue mejorada en la versión 2.4, permitiendo que los filtros sean aplicados incluso en el traspaso de las solicitudes entre las páginas (por medio de las directivas forward o include). Los diagramas siguientes muestran cómo operan los filtros en el flujo de procesamiento de una página en las dos versiones.



2. Ejemplos de Filtros
Considerando el flujo anterior, un Filtro se puede construir para realizar diversas tareas. Algunos ejemplos simples son los siguientes:
  • Validaciones de Seguridad. Validar que cada página cumpla una condición determinada, por ejemplo, que el usuario esté autenticado.
  • Compresión de Salidas/Entradas. Comprimir un archivo automáticamente para minimizar el tiempo de descarga.
  • Registro de Eventos. Generar un log de auditoría respecto con la información de los accesos a las páginas de la aplicación.
  • Modificación/Intervención de Salidas/Entradas. Eliminación de caracteres especiales, sobrantes, limpieza de HTML, etc.
La implementación de un filtro implica las siguientes actividades:
  • Construcción de la clase que implementará el filtro con la funcionalidad deseada.
  • Configuración del filtro en Tomcat para que realice su tarea
Obviamente, la construcción del filtro debe realizarse primero que la configuración, sin embargo, voy a describir la configuración primero porque es relevante para entender el "encadenamiento" de los filtros en el procesamiento y la construcción correspondiente.

3. Configuración del Filtro
La configuración del filtro se realiza en el archivo web.xml del contexto (dentro de la carpeta WEB-INF/). Se debe agregar una configuración como la siguiente para cada filtro que se quiera habilitar:

<filter>
  <filter-name>NOMBRE</filter-name>
  <filter-class>CLASE</filter-class>
  <init-param>
    <param-name>PARAM_1</param-name>
    <param-value>VALOR_1</param-value>
  </init-param>

  :
  <init-param>
    <param-name>PARAM_n</param-name>
    <param-value>VALOR_n</param-value>
  </init-param>
</filter>


<filter-mapping>
  <filter-name>NOMBRE</filter-name>
  <url-pattern>PATRON</url-pattern>
</filter-mapping>


3.1 Sección <filter>.
Permite configurar un filtro en el contexto.
  • NOMBRE. Corresponde a un nombre lógico que identifica la acción/objetivo que realiza el filtro. Por ejemplo: LoginFilter, RegExFilter, HitCountFilter, etc.
  • CLASE. Corresponde al nombre de la clase que implementa el filtro. Obviamente, debe ser una clase que esté disponible para el contexto de la aplicación.
  • PARAM_i y VALOR_i. Se pueden agregar parámetros de configuración relevantes para el filtro agregando nodos del tipo <init-param>. Cada nodo contiene un nombre (PARAM_i) y un valor (VALOR_i).
3.2 Sección <filter-mapping>.
Permite configurar a qué páginas se aplicará el filtro.
  • NOMBRE. Corresponde al nombre lógico del filtro que se indicó en la sección anterior. Debe ser el mismo para que aplique correctamente.
  • PATRON. Corresponde al patrón de páginas para las cuales se desea que se aplique el filtro. Un ejemplo es /* que indica que aplica a todas las páginas del contexto.
Un aspecto importante de la configuración en el archivo web.xml es que el orden en el que se declaran los filtros. Por ejemplo, supongamos que tenemos dos filtros: HitCountFilter y LoginFilter. El primero registra el número de accesos a las páginas del sitio y el segundo valida que un usuario esté autenticado. Veamos dos configuraciones posibles en el web.xml:


La configuración a) implica que primero se registrarán los accesos y luego se validará al usuario. La configuración b) implica que primero se validará al usuario y luego se registrarán los accesos.

Lo anterior se denomina encadenamiento de filtros (Filter Chain) y establece el orden en el que se deben aplicar los filtros. Lo importante de esto es que los filtros se aplican en cadena sobre la entrada y, luego, de manera inversa sobre la salida. Considerando el ejemplo a) anterior, se produce la siguiente secuencia:


La entrada (E1) es recibida y procesada por HitCounter. El resultado (E2) es entregado a LoginFilter quien la procesa. El resultado (E3) es entregado a la página.jsp para su procesamiento. A esta altura, la ejecución de la página.jsp es similar a la ejecución que se realizaría si no hubiera filtros configurados, es decir, la página.jsp se programa como una página normal, independiente de los filtros que podrían haber sido configurados previamente. El flujo de salida opera de la misma manera permitiendo realizar acciones sobre el resultado (output) de la página.jsp. Ejemplos de acciones posibles a este nivel son la compresión del HTML (eliminando espacios en blanco) o el reemplazo de contenido por otro utilizando expresiones regulares.

4. Construcción del Filtro 
Un filtro se construye heredando de la clase javax.servlet.Filter y, en él, se deben implementar tres métodos:
  • init(FilterConfig filterConfig).
    Este método se ejecuta cuando el contexto es cargado por el servidor y sólo se ejecuta una vez. El parámetro filterConfig permite acceder a las configuraciones que se hayan realizado para el filtro en el archivo web.xml por medio de la secciones <init-param/>. 
  • doFilter(ServletRequest request, ServletResponse response, FilterChain chain).
    Este método se ejecuta cuando se requiere aplicar el filtro a la entrada (request) y a la salida (response). El parámetro chain es el que permite traspasar el control al siguiente filtro en la cadena. En casos específicos, se puede interrumpir el flujo programáticamente. Por ejemplo, si el filtro LoginFilter identifica que el usuario no está autenticado, no es necesario ejecutar la página .jsp correspondiente.
  • destroy(). Este método se ejecuta cuando se baja el contexto y puede/debe ser utilizado para liberar recursos que se hayan utilizado para la operación del filtro. Por ejemplo, cachés, contadores, etc.
En la próxima parte describiré un ejemplo práctico de cómo hacer un filtro de autenticación de usuarios que permita validar, de una manera simple y centralizada, que el acceso a las páginas lo realicen únicamente usuarios que se hayan autenticado correctamente.