viernes, 24 de mayo de 2013

Seguridad en aplicación web con Apache Tapestry

Apache Tapestry
Apache Shiro
Además de la persistencia en una base de datos, otra de las funcionalidades comunes que suele necesitar una aplicación es la seguridad. En la seguridad hay varios aspectos a tratar que son:
  • Autenticación: que consiste en identificar al usuario en el sistema y comprobar que el usuario es quien dice ser. Normalmente la autenticación se suele realizar pidiéndole al usuario su identificativo, nombre de usuario o correo electrónico y una contraseña que solo él conoce. Aunque hay otras formas de realizarlo entre ellas los certificados.
  • Autorización: que consiste en determinar si el usuario autenticado tienen permisos para realizar una determinada operación. La autorización puede realizarse mediante roles, permisos o una combinación de ambos dependiendo de lo adecuado para la operación. Pero en ocasiones no solo hay que validar si un usuario tiene permisos para para realizar una acción, también puede ser necesario restringir la operación sobre ciertos datos, los que se determinen que él está autorizado a modificar, si no se hiciese esto un usuario podría alterar los datos de otro y el sistema tener una brecha de seguridad.
La autenticación y la autorización son solo dos aspectos a considerar pero no son suficientes para considerar una aplicación segura. Otros aspectos a tener en cuenta son XSS (Cross Site Scripting), la inyección de Sql o las conexiones cifradas SSL/TLS pero estos serán tema para otras entradas.

La información de autenticación y autorización puede guardarse en diferentes formas en lo que se conocen como Realms comunmente en Java. Algunos Realms puede ser simples archivos de texto plano aunque por su dificultad de mantenimiento al añadir nuevos usuarios, permisos o roles y que puede requerir un reinicio de la aplicación se suele optar por opciones como una base de datos relacional, un sistema LDAP o una base de datos nosql.

Para tener un sistema seguro no basta con ocultar las opciones que un usuario no puede realizar. Ocultar las opciones está bien pero también hay que realizar las comprobaciones de autorización en el caso de una aplicación web en el servidor, al igual que no basta con realizar las comprobaciones de validación de datos en el cliente con javascript, en ambos casos las comprobaciones hay que hacerlas en el lado del servidor también, de lo contrario nada impediría a un usuario conociendo la URL y datos adecuados a enviar realizar algo que no debería (advertido estás si no quieres que te llamen un sábado de madrugada).

La seguridad puede aplicarse de dos formas o una combinación de ambas:
  • De forma declarativa: ya sea mediante anotaciones o en un archivo independiente del código. Esta es la opción preferida ya que de esta manera el código de la aplicación no está mezclado con el aspecto de la seguridad.
  • De forma programática: si la opción declarativa no no es suficiente para algún caso podemos optar por hacerlo de forma programática, mediante código, con la que tendremos total flexibilidad para hacer cosas más específicas si necesitamos aunque mezclaremos el código de la aplicación con el código de seguridad.
Para aplicar seguridad en una aplicación Java disponemos de varias librerías, entre las más conocidas están:
Las dos librerías son similares aunque se comenta que Apache Shiro es más fácil de aprender. Además de integraciones con estas librerías Apache Tapestry dispone de módulos para realizar autenticación con servicios de terceros como Facebook, Twitter o sistemas OpenID.

Pero veamos como aplicar seguridad a una aplicación web que use el framework Apache Tapestry. En el ejemplo usaré el módulo tapestry-security que a su vez usa Apache Shiro. El ejemplo consiste un una página a la que solo los usuarios autenticados pueden acceder, en ella los usuarios podrán realizar varias operaciones: sumar a una cuenta uno, dos, tres, restar uno o poner la cuenta a cero en función de los permisos y roles que tengan. Para autenticase se usa un formulario aunque perfectamente podría usarse una autenticación BASIC.

Por simplicidad en el ejemplo los usuarios, passwords, roles y permisos los definiré en un archivo de texto, aunque en un proyecto real probablemente usaríamos una base de datos accediendo con hibernate para lo cual deberíamos implementar unos pocos métodos de la interfaz Realm o si necesitamos autorización la interfaz AuthorizingRealm de Shiro. El archivo shiro-users.properties sería el siguiente:

Por una parte se definen los usuarios con su password y roles que posee y por otro se definen los roles y los permisos que tiene cada uno.

La única configuración que deberemos indicarle a Tapestry es la URL de la página que autenticará a los usuarios y la página a mostrar en caso de que el usuario no esté autorizado para realizar alguna operación y el Realm a usar, lo hacemos añadiendo el siguiente código al módulo de la aplicación:

La página que realiza la autenticación es muy simple, poco más se encarga de recoger el usuario y password introducidos en el formulario de autenticación y a través del Subject realiza el inicio de sesión.

Una vez configurado el módulo y hecha la página que realiza la autenticación solo debemos usar de forma declarativa las anotaciones que proporciona Shiro, en el caso de la página Index @RequiresUser para permitir solo a los usuarios autenticados entrar a esa página y sobre las operaciones @RequiresPermissions para requerir ciertos permisos para ejecutar las acciones o @RequiresRoles para requerir ciertos roles. Estas anotaciones podemos usarlas no solo en las páginas y componentes de Tapestry que forman parte de la capa de presentación sino también en los servicios que desarrollemos y que forman la capa de lógica de negocio.

Si las anotaciones no son suficientes podemos hacerlo de forma programática, este es el probable caso de que un usuario solo debería modificar los datos relativos a él  sin poder modificar los de otros usuarios. El código variará en función de la forma de determinar si el usuario tiene permisos para un dato. Para comprobar si un usuario tiene ciertos permisos de forma programática debemos usar el objeto Subject que tiene muchos métodos para realizar comprobaciones, como para  reinicializar la cuenta se ha de tener el permiso «cuenta:reset» se debe hacer lo codificado en el método onActionFromReset:

En la plantilla de presentación que genera el html también deberemos hacer algunas comprobaciones de seguridad, básicamente para mostrar los enlaces a los que un usuario tenga permisos.

A continuación unas capturas de pantalla de la aplicación de la página de inicio autenticación, de la página con el índice de acciones y de la página de acción no autorizada:

Para hacer uso de tapestry-security deberemos incluir la librería como dependencia en el archivo build.gradle del proyecto:

Como en el resto de entradas el código fuente completo lo puedes encontrar en mi repositorio de GitHub. Si quieres probarlo en tu equipo lo puedes hacer de forma muy sencilla con los siguientes comandos y sin instalar nada previamente (salvo java y git). Si no dispones de git para clonar mi repositorio de GitHub puedes obtener el código fuente del repositorio en un archivo zip con el anterior enlace.

Para finalizar, a pesar de lo simple del ejemplo pero suficientemente representativo de lo que podría requerir una aplicación real comentar lo sencillo y limpio que ha sido aplicar la seguridad, por una parte gracias al uso de anotaciones y por otra gracias a Tapestry de por si.

Referencia:
Documentación sobre Apache Tapestry
http://tapestry.apache.org/security.html
http://tapestry.apache.org/
http://shiro.apache.org/
http://static.springsource.org/spring-security/site/index.html