Token con caducidad en Spring Security

Token con caducidad en Spring Security

0. Índice de contenidos.

1. Introducción

Hoy en día ya sabemos que los mecanismos de autenticación de las distintas aplicaciones pueden ser tan complejos o simples como podamos imáginar. Tenemos la autenticación básica que ofrecen los propios navegadores, la comunmente utilizada de un formulario de usuario y contraseńa, contra LDAP o Active Directory, etc. Incluso podemos tener combinación de varios de estos mecanismos de seguridad o hacer extensiones de los mismos.

Por suerte, SpringSecurity nos ofrece ya una amplia variedad de mecanismos de autenticación que nos simplifican gran parte del trabajo. Así que sólo tenemos que configurarlos y/o hacer nuestras propias extensiones o modificaciones en caso de ser necesario.

El objetivo de este tutorial es la introducción de un token con un tiempo de caducidad para que el proceso de login se realice en un periodo de tiempo máximo. Esto es útil cuando tenemos mecanismos de autenticación automáticos, de forma que limitamos la ventana de tiempo para el acceso a nuestra aplicación.

Antes de seguir, tengo que dejar claro que esto por sí mismo no se puede considerar un mecanismo de autenticación, realmente es un complemento a un mecanismo de autenticación ya existente. Por lo que nuestro sistema será tan seguro como sea ese mecanismo de autenticación; y con este token de caducidad lo que hacemos es aumentar un poco su seguridad limitando el tiempo en el que permitimos acceder a la aplicación.

Como el mecanismo de autenticación más extendido es el de un formulario con usuario y contraseńa, voy a tomar éste como mecanismo base. Extenderemos esta forma de autenticación ańadiendo un token con caducidad que establezca un tiempo máximo para el proceso de login, desde que se inicia pidiendo la autenticación al usuario, hasta que se recibe de este su usuario y contraseńa.

2. Entorno

Este tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2 GHz Intel Core i7, 8GB DDR3 SDRAM).
  • Sistema Operativo: Mac OS X Lion 10.7.1
  • Spring Security 3.1.0.RC2
  • JSF 2.1.2

3. Proceso de autenticación en Spring Security.

Antes de nada, voy a explicar brévemente cómo funciona el proceso de autenticación en Spring Security. Este proceso se basa en un conjunto de interfaces y clases que interactúan conjuntamente en un orden establecido para terminar decidiendo si una petición tiene o no acceso. Spring realiza esto mediante una cadena de filtros en los que se irán haciendo las distintas acciones y comprobaciones necesarias hasta decidir si la autenticación es correcta o no. En este proceso podemos identificar básicamente a 3 actores principales:

  • AuthenticationFilter: Es el responsable de crear una instancia concreta del usuario y sus credenciales de autenticación.
  • AuthenticationManager: Responsable de la validación del usuario y credenciales y rellenar los permisos/roles que tiene el usuario o de lanzar los distintos tipos de excepciones en caso de fallo en la autenticación. Esto lo hace apoyándose en uno o varios proveedores de autenticación.
  • AuthenticationProvider: Es en quien se delega la correcta validación del usuario y recuperación de roles.

Este proceso a alto nivel sería:

  • 1.- El filtro de seguridad intercepta la petición de autenticación. Crea una instancia de Authentication para su validación.
  • 2.- El filtro de seguridad pasa al AuthenticationManager el objeto creado para que realice su validación. El AuthenticationManager delegará esta validación al proveedor o proveedores de autenticación.
  • 3.- El proveedor de autenticación comprobará si los datos de autenticación son correctos. En caso de haber algún tipo de error lanzará una excepción.
  • 4.- El filtro de seguridad comprueba el resultado de la autenticación solicitada al AuthenticationManager. Si se produjo una excepción redirigirá el flujo al AuthenticationFailureHandler y si hubo éxito al AuthenticationSuccessHanlder.

4. Configuración de nuestro mecanismo de autenticación.

Como sabéis, en Spring utilizamos un fichero de configuración. Así que en el caso de Spring Security no iba a ser distinto. Aquí os pongo el fichero de configuración que vamos a utilizar en nuestro ejemplo, luego pasaré a contar cuál es cada uno de los elementos del fichero relacionados con el proceso de autenticación.

Respecto al mecanismo de seguridad en el fichero podemos ver:

  • Línea 16: Elemento que nos define cómo va a ser la autenticación de nuestra aplicación web por HTTP.
  • Línea 18: Establece la referencia al filtro de autenticación.
  • Línea 19: Se definen cuales són los recursos protegidos y quiénes tendrán acceso a ellos. En este caso se protege toda la aplicación y basta con que el usuario esté autenticado para que pueda acceder a los mismos.
  • Línea 22: Define cuál va a ser el punto de entrada para la autenticación. En este caso se redirige a la página /login.xhtml
  • Línea 27: Aquí definimos cual va a ser el filtro de autenticación responsable de formar un objeto con los parámetos recibidos por el usuario (username,password,timeout), pasarlo al AuthenticationManager para su validación y finalmente delegar el final del proceso dependiendo de si ha habido o no error de autenticación (authenticationSuccessHandler y authenticationFailureHandler).
  • Línea 38: Bean responsable de tratar los intentos de autenticación que han fallado y redirigir a la página de error.
  • Línea 44: Define cual va a ser el proveedor de autenticación responsable de comprobar que el usuario es válido y recuperar sus roles. En nuestro caso indicamos además el tiempo máximo para el proceso de login (nonceValiditySeconds) y la clave utilizada para generar una firma y evitar que el token pueda ser manipulado.
  • Línea 50: Indica que vamos a utilizar el AuthenticationManager por defecto de Spring y que este va a utilizar como proveedor de autenticación al que hemos definido en la línea 44.

5. Implementación del mecanismo de autenticación.

Pués como podéis ver en el fichero de configuración, tenemos alguna que otra clase que hay que implementar. El “EntryPoint” que utilizamos es el propio de Spring, así que lo primero que tenemos que implementar es el filtro. Como hemos dicho, vamos a hacer una extensión de la autenticación por usuario y contraseńa, así que como Spring ya nos ofrece un filtro para este tipo de autenticación, lo que hacemos es directamente heredar de él y sobreescribir aquello que nos haga falta. Básicamente lo realmente importante en el filtro es el método “attempAuthentication(..)”. Éste será casi identico al de Spring salvo por un par de detalles, además de recuperar el usuario y la contraseńa, se debe recuperar el timeout y con esto formará un objeto de autenticación que ya lleve informado el timeout. Así que el filtro quedará:

Como véis el filtro crea una instancia de un token de autenticación que además de usuario y contraseńa tiene un timeout. Este token lo implementamos también heredando del token que ofrece Spring para usuario y contraseńa, de forma que lo único que hacemos es extenderlo para que tenga el timeout.

Ahora nos vamos a encargar de los dos responsables del post-proceso de una autenticación con éxito o con error. Para esto también nos apoyamos en las clases de Spring de las cuáles extendemos nuestras propias clases.

En caso de éxito: (Se realizaría la lógica del intento de autenticación correcta y luego se redirige a una página de bienvenida)

Para el caso de un intento de autenticación errónea: (Se redirecciona a una página de error de autenticación o de timeout dependiendo del tipo de error)

Ahora ya sólo nos queda la implementación del proveedor de autenticación, que será el responsable de controlar que el token de caducidad sea correcto y que no se ha superado el timeout establecido. Para la generación y comprobación de este token, nos hemos basado en como Spring hace lo mismo para el tipo de autenticación DigestAuthentication, pero que no lo implementa en otros tipos de autenticación, por eso nos lo estamos teniendo que implementar nosotros.

La idea es generar un token de caducidad que no haga falta almacenar en el servidor para su posterior comprobación. Es decir, se meterá en un campo oculto del formulario y cuando éste se devuelva al servidor se comprobará su validez en el servidor. Para hacer esto tiene que ser autodefinido, es defir, debe contener la fecha de caducidad, y hay que protegerlo a posibles manipulaciones.

Así que nuestro proveedor de autenticación quedará de la siguiente forma:

  • Fecha máxima para el login: Cálculo con el tiempo actual en milisegundos más el valor de los segundos definidos como periodo de login
  • Firma del servidor para evitar manipulación: Se compone de un String con la fecha anteriormente calculada en milisegundos, más una clave propia del servidor sobre la que se hace un digest en MD5 en formato hexadecimal.

A la hora de recibir el formulario con el intento de autenticación, lo primero que se hace es validar el timeout con el método “validateTimeout()”, donde se recuperará el valor de la fecha máxima para el proceso de login y luego que aún no se ha superado. En el método “extractNonceValue(…)” se comprueba que éste parámetro no ha sido manipulado y que está correctamente informado, en caso contrario se lanza la excepción “BadCredentialsException”. Por último, ya solo basta comprobar que no se ha superado el tiempo para el proceso de login, método “isNonceExpired(…)”, que en caso de haberse superado se lanza la excepción “NonceExpiredException”.

Ya sólo nos falta crear el formulario de autenticación al que le pasaremos el cálculo de este token de caducidad. Primero creamos la página del formulario:

Como podéis ver en el campo oculto “timeout” hemos metido el valor que nos devuelve el controlador “LoginTimeout” en su método “getNonce(…)”. Este será de la siguiente forma:

Aquí podéis ver cómo en este controlador se inyecta el proveedor de autenticación (AutentiaAuthenticationProvider) para pedirle que genere el token de caducidad que luego él mismo deberá conprobar cuando se le envíe el formulario.

6. Conclusiones

Bueno, pues con este ejemplo se puede comprobar que gracias a la arquitectura de autenticación que tiene Spring Security, y a la implementación por defecto que ya trae, podemos realmente hacer muchas cosas en apenas unos pocos pasos. Como en el ejemplo propuesto, ańadiendo un token de caducidad generado en el servidor para el proceso de login.

Ya sabeís que esto es un ejemplo básico de lo que podemos hacer, pero espero que os sirva a alguno en vuestros casos particulares.

Saludos.