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.
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á:
La distribución de archivos en nuestra aplicación se observan en la figura 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.
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.
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:
Figura 3. Configuración del Pool de Conexión MySQL
Nota: Si la propiedad no se encuentra la podemos agregar.
Una vez configurado esto validamos que tengamos acceso a nuestra BD haciendo ping.
Figura 4. Ping satisfactorio.
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.
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:
Figura 6. Configuración de jdbcRealm
Una vez realizada toda la configuración procedemos a programar nuestra aplicación; explicaré los archivos más importantes:
<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.
<?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 .
<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>
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.
<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.
<?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>
Figura 8. Bienvenida.xhtml
Este archivo se mantuvo igual para ambos dominios ahora bien se agregó un hipervínculo para logout.
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.
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.
UPDATE IMPORTANTE: Esta post tienes fines didacticos, es importante que las contraseñas siempre se guarden en encriptadas en la base de datos, para ello se deben escoger los algoritmos de encriptación y digest en el jdbcRealm.