Seguridad en Java EE con JSF/JAAS – Dominio JDBC

Descripción General

En el siguiente post presentaré un ejemplo práctico de configuración del Java Authentication and Authorization Service (JAAS) API, a través de este API podemos aplicar seguridad a aplicaciones Java con poca configuración. Utilizaremos usuarios y grupos de seguridad para definir nuestro dominio.

Definiendo JDBC

JAAS permite definir dominios administrativos, de archivos, por certificado, JDBC y personalizados, en este ejemplo presentaré la configuración básica para JDBC. La plataforma que utilizaremos será:

  • IDE: Netbeans 8.0.1
  • JDK: 1.7.0_67
  • Servidor: Glassfish 4.1
  • BD: MySQL 5.5.8

La distribución de archivos en nuestra aplicación se observan en la figura 1.

post1_1
Figura 1. Distribución de archivos en el proyecto.

Contaremos con dos directorios protegidos protected, al que podrán acceder los usuarios del grupo de registereduser e invitedguest, y registered al que sólo podrán ingresar los usuarios del grupo registereduser.
En nuestra BD tendremos dos tablas credentials con los usuarios y groups con los grupos.

post1_2
Figura 2. Tablas de autenticación.

Lo primero que haremos será configurar nuestro recurso y pool de conexión JDBC. Para realizar la conexión con mysql se debe cargar al servidor el driver JDBC.
En este caso para MySQL se puede descargar de la url http://dev.mysql.com/downloads/connector/j/5.1.html, y luego copiamos el .jar a la ruta glassfish-4.1\glassfish\lib y reiniciamos el servidor.

Configuración de panel de Administración de Glassfish

En el panel de administración de glassfish accedemos a la ruta Resources->JDBC->Connection Pool, allí marcamos New para crear un nuevo pool.
Para el ejemplo lo llamaremos mydb donde los parámetros de configuración por pestaña son:

General:

  • Pool Name: mydb
  • Resource Type: javax.sql.DataSource
  • Datasource Classname: com.mysql.jdbc.jdbc2.optional.MysqlDataSource
post1_3
Figura 3. Configuración del Pool de Conexión MySQL

Additional Properties:

  • User: root
  • Password: root
  • DatabaseName: mydb
  • ServerName: 127.0.0.1
  • URL: jdbc:mysql://localhost:3306/mydb

Nota: Si la propiedad no se encuentra la podemos agregar.

Una vez configurado esto validamos que tengamos acceso a nuestra BD haciendo ping.

post1_4
Figura 4. Ping satisfactorio.

Configuración del recurso JDBC

Para configurar el recurso JDBC seguimos la ruta en glassfish Resources->JDBC->JDBC Resources, marcamos new y creamos la conexión al pool definido anteriormente.

post1_5
Figura 5. Configuración recurso JDBC

Por último nos falta configurar nuestro domino de seguridad, la configuración se hace en Configurations->Security->Realms, una vez allí podemos crear un nuevo dominio o reutilizar uno existente, en este ejemplo usaremos el dominio jdbcRealm, la configuración es la siguiente:

post1_6
Figura 6. Configuración de jdbcRealm

Properties:

  • JAAS Context: jdbcRealm
  • JNDI: jdbc/mydb
  • User Table: Credentials
  • User Name Column: username
  • Password Column: passwd
  • Group Name Column: groupname
  • Password Encryption Algorithm: none
  • Database User: root
  • Database Password: root
  • Digest Algorithm: none
  • Charset: UTF-8

Una vez realizada toda la configuración procedemos a programar nuestra aplicación; explicaré los archivos más importantes:

Archivo de configuración web.xml

<web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
    <context-param>
        <param-name>javax.faces.PROJECT_STAGE</param-name>
        <param-value>Development</param-value>
    </context-param>
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>/faces/*</url-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
    <security-constraint><!--Configuración de seguridad-->
        <web-resource-collection>
            <web-resource-name>
                Paginas protegidas
            </web-resource-name>
            <url-pattern>/faces/protected/*</url-pattern><!--Directorio restringido-->
            <url-pattern>/protected/*</url-pattern><!--Patron de páginas-->
        </web-resource-collection>
        <auth-constraint><!--Grupos permitidos -->
            <role-name>registereduser</role-name>
            <role-name>invitedguest</role-name>
        </auth-constraint>
    </security-constraint>
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>
                Paginas protegidas
            </web-resource-name>
            <url-pattern>/faces/registered/*</url-pattern>
            <url-pattern>/registered/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>registereduser</role-name>
        </auth-constraint>
    </security-constraint>
    <login-config>
        <auth-method>FORM</auth-method><!--Método de autenticación-->
        <realm-name>jdbcRealm</realm-name><!--Nombre de dominio-->
        <form-login-config>
            <form-login-page>/login.html</form-login-page><!--Página de login-->
            <form-error-page>/noautorizado.html</form-error-page><!--Página de error de autenticación-->
        </form-login-config>
    </login-config>
    <security-role><!--Roles de autenticación-->
        <role-name>registereduser</role-name>
    </security-role>
    <security-role>
        <role-name>invitedguest</role-name>
    </security-role>
</web-app>

En configuramos los directorios protegidos y los grupos permitidos. Con indicamos el tipo de login (form, basic…), el dominio y las páginas de login, por último con configuramos los roles.

Archivo de configuración sun-web.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD GlassFish Application Server 3.0 Servlet 3.0//EN" "http://www.sun.com/software/appserver/dtds/sun-web-app_3_0-0.dtd">
<sun-web-app error-url="">
    <security-role-mapping><!--Mapeo de roles y grupos-->
        <role-name>invitedguest</role-name>
        <group-name>invitedguest</group-name>
    </security-role-mapping>
    <security-role-mapping>
        <role-name>registereduser</role-name>
        <group-name>registereduser</group-name>
    </security-role-mapping>
    <class-loader delegate="true"/>
    <jsp-config>
        <property name="keepgenerated" value="true">
            <description>Keep a copy of the generated servlet class' java code.</description>
        </property>
    </jsp-config>
</sun-web-app>

El archivo sun-web.xml es propio de glassfish, en caso de que usemos otro servidor las etiquetas pueden cambiar, aquí mapeamos los roles y los grupos con .

Archivo Index.html

<html>
    <head>
        <title>Inicio</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
            Click <a href="faces/protected/bienvenida.xhtml"> en este link
            </a> para acceder a información protegida
            Click <a href="faces/registered/bienvenida.xhtml"> en este link
            </a> solo para usuarios registrados
    </body>
</html>
post1_7
Figura 7. Index.html

En el archivo index tenemos dos hipervínculos a archivos de los directorios protegidos, con nuestra configuración al marcar uno de ellos se nos solicitará el login, nótese que la extensión es html, pero bien puede ser jsp o xhtml.

Archivo login.html

<html>
    <head>
        <title>Formulario de login</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <form method="post" action="j_security_check">
            </table>
                <tr>
                    <td>Nombre: </td>
                    <td><input type="text" name="j_username"/></td>
                </tr>
                <tr>
                    <td>Contraseña: </td>
                    <td><input type="text" name="j_password"/></td>
                </tr>
            </table>
            <input type="submit" value="Login">
        </form>
    </body>
</html>

El archivo de login define la autenticación del usuario e igualmente la extensión es html, pero puede ser jsp o xhtml, los parámetros j_security_check, j_username y j_password son estándares de JAAS y deben mantenerse para la autenticación.

Archivo bienvenida.xhtml

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html">
    <h:head>
        <title>Bienvenida</title>
    </h:head>
    <h:body>
        <h:form>
            Bienvenida Usuario Registrado            
            Acceso concedido            
            Usuario: #{usuario.nombre}            
            <h:commandLink value="Logout" actionListener="#{usuario.logout()}" action="bienvenida"/>
        </h:form>
    </h:body>
</html>
post1_8
Figura 8. Bienvenida.xhtml

Este archivo se mantuvo igual para ambos dominios ahora bien se agregó un hipervínculo para logout.

Archivo Usuario.java

import java.io.IOException;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@ManagedBean(name = "usuario")
@SessionScoped
public class Usuario implements Serializable {

    private String nombre;

    public String getNombre() {
        if (nombre == null) {
            getDatosUsuario();
        }
        return nombre == null ? "" : nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }
    /**
     * Obtiene el nombre del usuario de la sesión
     */
    private void getDatosUsuario() {
        ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
        Object objPeticion = context.getRequest();
        if (!(objPeticion instanceof HttpServletRequest)) {
            System.out.println("Error objeto es: "
                    + objPeticion.getClass().toString());
            return;
        }
        HttpServletRequest peticion = (HttpServletRequest) objPeticion;
        nombre = peticion.getRemoteUser();
        if (nombre == null) {
            logout();
        }
    }
    /**
     * Invalida la Sesión y redigiré a la página de inicio
     */
    public void logout() {
        ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
        ec.invalidateSession();
        try {
            ec.redirect(ec.getRequestContextPath());
        } catch (IOException ex) {
            Logger.getLogger(Usuario.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
} 

En el Bean Usuario se usan los métodos getDatosUsuario y logout para obtener los datos del usuario y cerrar sesión.

Conclusión

Para concluir el post quiero enfatizar que es sólo un ejemplo básico, el nivel de parámetros de configuración y potencial que podemos aprovechar de JAAS es muy amplio.

Pueden descargar una copia completa del proyecto y definición de la base de datos del repositorio github https://github.com/earth001/Ejercicios-JavaEE-Seguridad.

3 comentarios en “Seguridad en Java EE con JSF/JAAS – Dominio JDBC

Deja un comentario

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