lunes, enero 26, 2009

Login de un usuario mediante Realm en un servidor Java EE

La seguridad en las aplicaciones Web es un tema muy importante a la vez que recurrente. En la mayoría de aplicaciones que he visto o en las que me ha tocado trabajar se ha usado la típica de presentar al usuario un formulario para que introdujese su usuario y contraseña, validarlos contra una base de datos y una vez que se determinaba que las credenciales eran correctas, de una u otra forma, arrastrar esa información por la sesión de la aplicación.

Considero que es una forma muy poco elegante de habilitar la seguridad de una aplicación. Más aún cuando existen otras formas más estándares de validar un usuario en una aplicación. En concreto mediante el uso de Realm (también conocido por Security Police Domain). Este mecanismo forma parte de la especificación JEE v.5, por lo que cualquier servidor de aplicaciones “decente” nos proporcionará los mecanismos necesarios para su implantación y uso.

Dentro de Realm existen a su vez varias alternativas de uso:

  • Digest

  • Client certificate

  • Basic

  • Form



Sin entrar en detalle en cada una de ellas, se puede decir que tanto la Basic como la Digest no son muy aconsejadas.

Este documento se centrará en el tipo Form en un servidor Tomtat 6.0 y usará una base de datos Oracle 9i para validar las credenciales del usuario (existen otras posibilidades, como validar contra un LDAP o un fichero de texto, por ejemplo).

Este tipo de Realm se caracteriza por la presentación al usuario de una página personalizada para el login (a diferencia de la basic/digest, que nos presenta el típico cuadro de diálogo del navegador) cuando este intenta acceder a una URL que consideramos restringida. En este caso, vamos a restringir el acceso a toda la aplicación.

Para habilitar este tipo de seguridad, es necesario la modificación de dos ficheros. Uno es el web.xml (conocido por todos) y otro es el context.xml (bajo el directorio META-INF). De este fichero ya hemos hablado en otra entrada, cuando tratamos la configuración de un pool de conexiones. Es un fichero que se usa para la configuración de recursos de la aplicación de una forma limpia.

En este fichero hemos de crear una entrada parecida a la siguiente:


<realm classname="org.apache.catalina.realm.JDBCRealm"
drivername="oracle.jdbc.OracleDriver"
connectionurl="jdbc:oracle:thin:@nombre_servidor:puerto:SID" digest="MD5"
connectionname="usu_db" connectionpassword="pas_db"
usertable="esquema.nombre_tabla_usuarios" usernamecol="nombre_columna_login"
usercredcol="nombre_columna_password" userroletable="esquema.nombre_tabla_roles"
rolenamecol="nombre_columna_rol">



Veamos un poco más en detalle qué significa cada atributo.

  • className
    Nombre de la clase que el servidor usará para establecer una conexión a la base de datos. Usaremos esta siempre que queramos usar un JDBC en un servidor Tomcat, claro.

  • driverName
    Nombre del driver que se usará para la conexión.

  • connectionURL
    No hay mucho que explicar sobre este punto... simplemente la cadena de conexión usada.

  • digest
    En una aplicación medianamente seria, las contraseñas de los usuarios no se guardarán en formato plano que cualquier “humano” pueda leer, sino que se “pasarán” por algún tipo de algoritmo de encriptación tipo sha o similar. En este caso se ha usado el MD5.

  • connectionName
    Nombre del usuario de la base de datos.

  • connectionPassword
    Contraseña del usuario de la base de datos.


Aquí llega lo interesante. Realm se basa en la validación de usuarios y roles para establecer los permisos a la aplicación (como podremos comprobar en la configuración del fichero web.xml). Por ello, se requiere una tabla de usuarios y otra de roles en las que se buscará al usuario y se recuperarán los roles que tiene asignados.

Siempre que uso Realm utilizo una vista en la que muestro únicamente tres columnas: nombre_usuario, clave y nombre_rol. De este modo queda un poco más “autónomo” de posibles cambios y libre de las restricciones que Realm requiere en las relaciones de las dos tablas.


  • userTable
    Nombre de la tabla que contiene los registros de los usuarios (en mi caso, nombre de la vista que tiene los datos del login, contraseña y rol del usuario).

  • userNameCol
    Nombre de la columna donde se encuentra el “login” del usuario.

  • userCredCol
    Nombre de la columna donde se encuentra la contraseña del usuario.

  • userRoleTable
    Nombre de la tabla que contiene los registros de los roles (en mi caso, nombre de la vista que tiene los datos del login, contraseña y rol del usuario).

  • roleNameCol
    Nombre de la columna donde se encuentra el nombre del rol.


Bien, con esto habríamos terminado con el fichero config.xml. Sólo una nota. Podríamos haber utilizado una conexión directa (sin usar JDBC) a la base de datos o también podríamos usar un “data source” ya existente (el elemento Realm cambia, en los dos casos, en sus atributos). En el caso de usar un data source existente este debería estar definido en el server.xml del servidor Tomcat o en el web.xml de la aplicación. Ya que si hacemos referencia a uno declarado en el mismo fichero config.xml no funcionaría.

Ahora necesitamos realizar unos cambios en el fichero web.xml. Deberemos añadir algo parecido a esto (asegurate de que respetas el orden de los elementos en el fichero web.xml):



<security-constraint>

<display-name>Constraint1</display-name>

<web-resource-collection>

<web-resource-name>NombreRecurso</web-resource-name>

<description>

<url-pattern>/*</url-pattern>

<http-method>GET</http-method>

<http-method>POST</http-method>

</description>

<auth-constraint>

<description>

<role-name>nombre_rol1</role-name>

<role-name>nombre_rol2</role-name>

</description>

</auth-constraint>

<login-config>

<auth-method>FORM</auth-method>

<realm-name>

<form-login-config>

<form-login-page>/login.html</form-login-page>

<form-error-page>/login_error.html</form-error-page>

</form-login-config>

</realm-name>

<security-role>

<description>

<role-name>nombre_rol1</role-name>

</description>

<security-role>

<description>

<role-name>nombre_rol2</role-name>

</description>



Para explicarlo, podemos dividirla en tres grupos. El primero sería el grupo de “security-constrain”. Bien, en este establece establece a través de patrones qué recursos de nuestra aplicación estarán restringidos (elementos url-pattern. Puede haber más de uno. En este caso es toda la aplicación, pero podría usarse, por ejemplo “pages/*.jsp u otras... atención a la barra inicial), bajo qué tipo de petición http (elementos http-method) y qué roles serán los que tengan acceso a esos recursos (elementos role-name).

El segundo grupo define que tipo de autenticación estamos usando (elemento auth-method) y, muy importante, cuál será la página que se presentará para la introducción de las credenciales del usuario en caso de que se detecte un acceso restringido (elemento form-login-page) y cuál la que se presentará en caso de producirse un error en la validación de las credenciales (elemento form-error-page).

El tercer grupo define los roles de los usuarios activos para la aplicación (elementos role-name). Estos deben ser los mismos que aparezcan en el campo nombre_rol de la tabla nombre_tabla_rol que hemos establecido en el fichero config.xml.

Bien, con todo esto configurado, sólo nos queda reiniciar nuestro Tomcat y acceder a la raíz de nuestra aplicación. Si todo está correcto, nos deberá aparecer la página de login que hemos establecido en la configuración.

Un detalle a tener en cuenta es que, al tener toda nuestra aplicación con acceso restringido, también los recursos (estilos, imágenes...) que use nuestra página de login (e incluso la propia página) también estarán restringidos, por lo que quizás tengas que darle un par de vueltas al diseño para conseguir que “luzca” como de costumbre.

Una vez valides correctamente a un usuario a través de su login y contraseña podrás recuperar el login introducido a través del método "getRemoteUser()" del objeto HttpServletRequest y recuperar el resto de sus datos o tratarlo como creas conveniente. Lo que es seguro es que nadie llegará a una página que consideres con acceso restringido sin pasar antes por el login.

Ahora, sólo os queda comenzar a probar con distintas configuraciones para establecer distintos niveles de acceso a los usuarios. Que lo disfruteis.

9 comentarios:

Anónimo dijo...

Muchas gracias por el aporte, pero no hablas de los listener, ni acaba de funcionarme, tampoco estan las etiquetas de los xml cerradas, si tienes una fuente a partir de donde escribiste el articulo, por favor publicala a ver si arroja un poco mas de luz.

FiTe dijo...

Hola tío,

No entiendo porqué he de hablar de los listener... bajo mi punto de vista son cosas distintas que se deben usar para tareas distintas.

Con tan poca información sobre los problemas que tienes, es muy difícil que te ayude. ¿Qué es lo que no te acaba de funcionar? ¿Qué errores te aparecen? ¿Dónde?

Bueno, el que las etiquetas no estén cerradas no tienen porqué ser un problema... no todo va a ser "copiar y pegar", no? :P

Zorno dijo...

Buen artículo.

sawbona dijo...

Hola fite, me ayudó mucho tu artículo. Aunque me perdí un poco al principio porque en la etiqueta de < realm-name > te faltó poner el nombre del realm que ya habías declarado, creo. Por lo demás genial me fue de mucha ayuda. Saludos.

Anónimo dijo...

Hola muy interesante tu informacion pero me gustaria saber si es posible hacer esto en una aplicacion de escritorio o si tienes algun link de donde obtener esa informacion aparte del de sun.
Mi correo es c_ordenes03@hotmail.com

Anónimo dijo...

Muy buen artículo. Solo hay un detalle, en context.xml está mal cerrada la etiqueta Realm; debería ser así:

<Realm className="org.apache.catalina.realm.JAASRealm"
appName="CustomLogin"
userClassNames="com.aradne.jaas.UserPrincipal"
roleClassNames="com.aradne.jaas.RolePrincipal" />

Saludos,
Larry Guerra

diseño web dijo...

fite gracias por el post esta muy bueno

leonelf91 dijo...

Buenas, una consulta :
Es posible con este sistema de autenticacion que el usuario pueda contar con una cuenta administrador y una interfaz que le permita otorgar permisos de acceso a pantallas a otros roles ?.
Gracias de antemano.
Saludos.

Juanjo C dijo...

Hola leonelf91,

Por supuesto que puedes crear esa funcionalidad. Ten en cuenta que estás usando una autenticación contra una bb.dd.

Puedes crear unas páginas que te permitan seleccionar/crear usuarios y asignar estos a roles. Guardando estos datos en la bb.dd. y, siempre y cuando hayas securizado el acceso a las páginas a las que quieres que tengan acceso los nuevos usuarios, funcionaría.

Recuerda también securizar el acceso a las páginas de administración que te permiten asignar estos nuevos roles, de lo contrario, cualquiera podría hacerlo.

Un saludo.