2. Ciclo de vida de los enterprise beans y seguridad de acceso
Vamos a incluir en esta sesión dos apartados que no tiene mucha relación, pero que tienen una extensión adecuada para cubrirlos ambos en una única sesión. Son el ciclo de vida y las restricciones de seguridad en el uso de los enterprise beans.
2.1. Ciclo de vida
Hemos visto que los enterprise beans son objetos gestionados por el servidor de aplicaciones. Esto significa que nosotros no vamos a poder llamar a new()
para sólo vamos a poder obtener referencias para su uso. Es el servidor de aplicaciones se encarga de crear las instancias de los enterprise beans, de inyectar sus referencias en las variables anotadas y de controlar sus distintas fases del ciclo de vida.
Empezamos con el ciclo de vida de los beans de sesión con estado, el más elaborado, y seguimos con el de los beans sin estado.
2.1.1. Ciclo de vida de los beans de sesión con estado
Recordemos un ejemplo de enterprise bean con estado:
@Stateful
public class SaludoConEstadoServicio {
ArrayList<String> saludos = new ArrayList<String>(); (1)
@EJB
SaludoServicio saludoServicio; (2)
public String saludo(String nombre) {
String saludo = saludoServicio.saludo(nombre); (3)
saludos.add(saludo);
return saludo;
}
public ArrayList<String> saludos() {
return saludos;
}
}
1 | Estado local definido por un ArrayList donde se guardan saludos |
2 | Se obtiene una referencia al bean SaludoServicio para obtener el saludo |
3 | Se obtiene un saludo invocando a SaludoServicio y se guarda en el array de saludos |
En los beans con estado el servidor debe garantizar que el cliente se comunica siempre con la misma instancia del bean, ya que el cliente va modificando su estado. Todas las invocaciones a métodos del bean de un mismo cliente deben ser respondidas por la misma instancia, que es la que guarda el estado modificado. Cada cliente se conectará con una instancia distinta.
El ciclo de vida de los beans de sesión con estado debe por tanto garantizar esta característica. Se muestra en la siguiente figura:

-
El contenedor crea una instancia usando el constructor por defecto cuando comienza una nueva sesión con un cliente.
-
Después de que el constructor se ha completado, el contenedor inyecta los recursos tales como contextos JPA, fuentes de datos y otros beans.
-
La instancia se almacena en memoria, esperando la invocación de alguno de sus métodos.
-
El cliente ejecuta un método de negocio y el contenedor lo invoca en el bean
-
Espera hasta que los siguientes métodos son invocados y los ejecuta.
-
Si el cliente permanece ocioso por un periodo de tiempo, el contenedor pasiva el bean, serializándolo y guardándolo en disco.
-
Si el cliente vuelve a invocar a un bean pasivado, éste se activa (el objeto es leído del disco) y se ejecuta el método
-
Si el cliente no invoca un método sobre el bean durante un cierto periodo de tiempo, el bean es destruido.
-
Si el cliente invoca algún método con el atributo
@Remove
el bean es destruido.
Es posible definir métodos de callback del ciclo de vida, a los que el contenedor llamará antes o después de cierto momento del ciclo de vida, con las siguientes anotaciones. Son muy útiles para gestionar la apertura y cierre de recursos inyectados y usados por el bean.
-
@PostConstruct
: el método con esta anotación se invoca justo después de que se ha ejecutado el constructor por defecto y de que se han inyectado los recursos. -
@PrePassivate
: invocado antes de que un bean sea pasivado. -
@PostActivate
: invocado después de que el bean haya sido traído a memoria por el contenedor y antes de que se ejecute cualquier método de negocio invocado por el cliente -
@PreDestroy
: invocado después de que haya expirado el tiempo de conexión o el cliente haya invocado a un método anotado con@Remove
. Despés de invocar al método, la instancia del bean se elimina y se pasa al recolector de basura.
Las anotaciones @PrePassivate
y @PostActivate
tienen que ver con la actualización de recursos que no pueden ser serializados. Por ejemplo, un objeto java.sql.Connection
no puede ser serializado y debe ser reiniciado cuando el bean se reactiva.
2.1.2. Ciclo de vida de los beans de sesión sin estado
Los beans de sesión sin estado tienen un ciclo de vida muy sencillo. O existen o no existen. Una vez que el bean es creado, se coloca en un pool para servir las peticiones de los clientes. En algún momento el bean se destruye, o bien cuando se reduce la carga del servidor o cuando la aplicación se cierra. El contenedor hace lo siguiente:
-
Crea una instancia del bean usando el constructor por defecto.
-
Inyecta recursos como proveedores de JPA o conexiones de bases de datos y llama a los métoods etiquetados con
@PostConstruct
. -
Coloca instancias del bean en un pool.
-
Saca del pool una instancia ociosa cuando recibe una petición de ejecución de un método por el cliente.
-
Ejecuta el método de negocio solicitado por el cliente.
-
Cuando el método se termina de ejecutar, el bean se vuelve a colocar en el pool.
-
Cuando el contenedor lo considera necesario elimina el bean del pool y llama a los métodos
@PreDestroy
antes de colocarlo en el recolector de basura.
Los beans de sesión sin estado también soportan las anotaciones @PostConstruct
y @PreDestroy
.
La siguiente figura refleja este ciclo de vida:

2.2. Acceso concurrente a los beans singleton
Es posible gestionar el acceso concurrente de los clientes usando la concurrencia gestionada por el contenedor o la concurrencia gestionada por el bean. Se definen para ello las anotaciones @ConcurrencyManagement(ConcurrencyManagementType.BEAN)
y @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
que hay que definir a nivel de clase.
Esta última es la menos frecuente y deja en manos del desarrollador la gestión de la concurrencia usando construcciones de Java como synchronized
o volatile
.
En la concurrencia gestionada por el contenedor, el contenedor se encarga de gestionar el acceso concurrente de los clientes a los métodos del bean. El contenedor realiza un bloque a nivel de métodos, asignádoles un bloqueo de tipo Read
(compartido) o Write
(exclusivo). Un bloqueo Read
en un método permite las invocaciones concurrentes del método. Un bloqueo Write
espera al procesamiento de una invocación antes de comenzar la siguiente. Si no especificamos nada en un método, por defecto tiene el bloque Write
.
Estos tipos de bloqueos se especifican con las anotaciones @Lock(LockType.READ)
y @Lock(LockType.WRITE)
que se pueden realizar a nivel de clase o de método de negocio. El valor especificado en un método sobreescribe el de la clase.
En el caso del bloqueo exclusivo es posible definir un time-out para que se genere una excepción si el bloqueo dura más de un determinado tiempo usando la anotación AccessTimeout
:
@Singleton
public class Foo {
@Lock(LockType.WRITE)
@AccessTimeout(value=1,unit=TimeUnit.MINUTES)
public void TestMethod() {
//...
}
}
Es posible especificar las unidades (por defecto son MILLISECONDS):
-
NANOSECONDS
-
MICROSECONDS
-
MILLISECONDS
-
SECONDS
-
MINUTES
-
HOURS
2.3. Seguridad en la arquitectura EJB
2.3.1. Introducción a la seguridad en EJB
La seguridad es un aspecto fundamental de las aplicaciones empresariales. Cualquier aplicación interna o accesible via web va a intentar ser hackeada por extraños. Podemos analizar tres elementos fundamentales en la seguridad de una aplicación:
- Autentificación
-
Dicho sencillamente, la autentificación valida la identidad del usuario. La forma más común de autentificación es una simple ventana de login que pide un nombre de usuario y una contraseña. Una vez que los usuarios han pasado a través del sistema de autentificación, pueden usar el sistema libremente, hasta el nivel que les permita el control de acceso. La autentificación se puede basar también en tarjetas de identificación, certificados y en otros tipos de identificación.
- Control de acceso
-
El control de acceso (también conocido como autorización) aplica políticas de seguridad que regulan lo que un usuario específico puede y no puede hacer en el sistema. El control de acceso asegura que los usuarios accedan sólo a aquellos recursos y operaciones a los que se les ha dado permiso. El control de acceso puede restringir el acceso de un usuario a subistemas, datos, y objetos de negocio. Por ejemplo, a algunos usuarios se les puede dar permiso de modificar información, mientras que otros sólo tienen permiso de visualizarla.
- Comunicación segura
-
Los canales de comunicación entre un cliente y un servidor son un elemento muy importante en la seguridad del sistema. Un canal de comunicación puede hacerse seguro mediante aislamiento físico (por ejemplo, via una conexión de red dedicada) o por medio de la encriptación de la comunicación entre el cliente y el servidor. El aislamiento físico es caro, limita las posibilidades del sistema y es casi imposible en Internet, por lo que lo más usual es la encriptación. Cuando la comunicación se asegura mediante la encriptación, los mensajes se codifican de forma que no puedan ser leídos ni manipulados por individuos no autorizados. Esto se suele consigue mediante el intercambio de claves criptográficas entre el cliente y el servidor. Las claves permiten al receptor del mensaje decodificarlo y leerlo.
La seguridad debe aplicarse tanto a la vista como a la capa de negocio. El hecho de que un usuario no pueda acceder a una página no significa que un la lógica de negocio no pueda invocarse. Un hacker puede conseguir acceder a los servicios web REST y hacer peticiones no autorizadas. O un programador de la capa web puede cometer un error y llamar desde una página sin el nivel de autorización necesaria a una operación restringida de la lógica de negocio. La arquitectura EJB proporciona un modelo de seguridad flexible y elegante que permite garantizar la seguridad en el acceso a los métodos de negocio de la aplicación enterprise.
La mayoría de los servidores EJB soportan la comunicación segura a través del protocolo SSL (Secure Socket Layer) y proporcionan algún mecanismo de autentificación, pero la especificación Enterprise JavaBeans sólo especifica el control de acceso a los enterprise beans.
En este apartado veremos qué mecanismos define la especificación EJB para el control de acceso a los enterprise beans. Veremos que es posible definir un control de acceso declarativo y programativo a los métodos de los beans, de forma que sólo aquellos usuarios autorizados puedan ejecutar el código restringido. Antes de eso, revisemos brevemente cómo realizar la autentificación del usuario.
2.3.2. Realms, Users, Groups y Roles
Uuarios, grupos y roles son tres conceptos relacionados que forman la base de la seguridad EJB.
El usuario es cualquier persona autentificada en el sistema. Normalmente es un nombre de usuario autentifcado mediante una contraseña.
El concepto de grupo es similar al de sistema de ficheros UNIX. Un grupo se define en el servidor de aplicaciones como un conjunto de usuarios. Por ejemplo, podemos definir el grupo de "administrador" al que pertenecerán todos los usuarios que sean administradores de las aplicaciones desplegadas.
Los roles se definen en las aplicaciones y es la base de la autorización del acceso a los métodos de negocio. Sólo aquellos usuarios o grupos que tengan un determinado rol podrán acceder a determinados métodos del EJB. Se deberá mapear usuario y grupos con roles. La separación entre usuarios/grupos y roles permite que la aplicación se codifique de forma indenpendiente al entorno en el que se va a desplegar. Una aplicación puede tener un rol "Administrador" y en la compañía en la que se despliega podemos tener en el directorio LDAP un grupo "Gestor Aplicaciones" que sea equivalente. El mapeo entre grupos/usuarios y roles lo realiza el servidor de aplicaciones.

Un realm se define en el servidor de aplicaciones como un conjunto de usuarios y grupos definidos mediante un determinado mecanismo.
Los dos realms que tenemos por defecto en WildFly son ManagementRealm
y ApplicationRealm
. ManagementRealm
se utiliza para la aplicación de administración del servidor, por lo que sólo nos permite controlar la autentificación (no se indican roles porque el único rol que tienen los usuarios de este conjunto es el de administrar el servidor). Por otro lado, ApplicationRealm
nos permite además controlar la autorización, mediante la asignación de roles a usuarios.
Como ya vimos en el módulo de Componentes Web, es posible añadir nuevos usuarios en estos realms con el script de WidlFly addUser.sh
situado en $WILDFLY_HOME/bin
:
$ ./addUser.sh
Nos preguntará en primer lugar en cuál de los dos realms por defecto queremos introducir el usuario, y a continuación nos irá pidiendo los datos del nuevo usuario. El último de los datos que pide es la lista de roles permitidos separados por coma. En las aplicaciones que incorporen seguridad declarativa se nos permitirá entrar con cualquiera de los usuarios del ApplicationRealm
. Las operaciones que nos permita hacer dependerán de los roles asignados.
2.3.3. JAAS: Servicio Java de Autentificación y Autorización
La seguridad en Java EE está basada en el Servicio Java de Autentificación y Autorización (JAAS). Esta arquitectura se introdujo en la versión 1.4 de Java EE y desde entonces ha sido adoptado ampliamente por la industria Java comercial y open source. La versión más reciente es la JSR 196 (Java Authentication Service Provider Interface for Containers), definida en Java EE 6. Las APIs más importantes son las definidas por los paquetes:
JAAS separa el sistema de autentificación de la aplicación Jaava EE mediante el uso de un API bien definida que es implementada por el servidor de aplicaciones. La aplicación Java EE no se debe preocupar de detalles de bajo nivel como trabajar con los algoritmos de encriptación de contraseñas o comunicarse con servicios externos de autentificación como el Active Directory de Microsoft o el LDAP. El servidor de aplicaciones que contiene la aplicación Java EE se encarga de ello.
JAAS está diseñado para que la autentificación y autorización puede realizarse en cualquier capa Java EE, incluyendo la capa web y la capa EJB. En la realidad, sin embargo, la mayoría de aplicaciones Java EE son accesibles via web y comparten un único sistema de autentificación a lo largo de todas las capas. JAAS permite propagar la autentificación realizada en la capa web a cualquier otra capa de Java EE. Una vez autentificado el usuario, el contexto de autentificación se pasa a todas las capas siempre que sea posible, en lugar de repetir los pasos de autentificación en cada una de ellas. El objeto Java Principal
representa este usuario autentificado compartible entre capas. La siguiente figura representa este mecanismo:

Como se muestra en la figura, un usuario se logea en la aplicación a través de la capa web. La capa web obtiene la información de autenficiación del usuario (login y password normalmente) y los autentifica usando JAAS contra el realm definido en el servidor de aplicaciones. Si se valida la autentificación se obtiene un Principal
que se asocia con uno o más roles de la aplicación. Para cada recurso restringido de la capa web o la capa EJB, el servidor de aplicaciones chequea si el principal/role esta autorizado para acceder al recurso. El Principal
se pasa a de la capa web a la capa EJB cuando se realiza una invocación de un método de algún bean.
Autentificación en la capa web
En la asignatura Componentes Web ya estudiamos con detalle la autentificación en la capa web. Recordemos que hay que realizar la configuración de seguridad en el fichero web.xml
, y que podemos escoger entre tres tipos de autentificación: FORM, BASIC y CLIENT-CERT. Con el método FORM podemos especificar una página HTML en la que se define un formulario que el usuario utilizará para logearse. En el formulario se utilizarán los nombres estándar de parámetros j_username
y j_password
para guardar en ellos el usuario y su contraseña y se llamará a la acción estándar j_security_check
.
El método BASIC proporciona un mecanismo de autentificación básico, basado en cabeceras de autentificación para solicitar datos del usuario (el servidor) y para enviar los datos del usuario (el cliente). Para la comunicación se debería utilizar el protocolo SSL y la contraseña se envía codificada con el método Base64. Será el que utilicemos en los ejemplos por ser el más sencillo.
Con el método CLIENT-CERT no es necesario pedir el usuario/contraseña sino que el cliente envía al servidor de aplicaciones un certificado de clave pública almacenado en el navegador utilizando SSL y el servidor autentifica el contenido del certificado. El proveedor JAAS valida a continuación las credenciales.
En el mismo fichero web.xml
se definen los roles y las restricciones de acceso a recursos (URLs) definidos en los servlets o páginas JSP. Por ejemplo, el siguiente código declara una restricción de acceso a la URL /holamundorestringido
a los usuarios con el rol User
. Como el método de autentificación es BASIC, el navegador pedirá en una ventana de diálogo el usuario y la contraseña. Si utilizamos una ventana normal del navegador éste guardará la configuración de usuario y contraseña una vez introducida por primera vez. Para probar más de una vez podemos abrir una ventana de incógnito.
<?xml version="1.0" encoding="UTF-8"?>
<web-app 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"
version="3.1">
<security-constraint> (1)
<web-resource-collection>
<web-resource-name>holamundorestringido</web-resource-name>
<url-pattern>/holamundorestringido</url-pattern> (2)
</web-resource-collection>
<auth-constraint>
<role-name>usuario-saludo</role-name> (3)
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method> (4)
</login-config>
<security-role> (5)
<role-name>usuario-saludo</role-name>
</security-role>
</web-app>
1 | Declaración de restricción de seguridad |
2 | URL del recurso que se restringe |
3 | Rol/grupo del usuario autorizado |
4 | Tipo de autentificación BASIC |
5 | Declaración de todos los roles que se utilizan en las restricciones (en el caso en que haya más de un recurso autorizado a otros roles, aquí se deben listar todos |
La autentificación BASIC
hace que el navegador pregunta al usuario por un login y una contraseña. El navegador envía esa identidad al servidor de aplicaciones, que la valida contra la lista de usuarios. Si el usuario y la contraseña coinciden con la registrada en el servidor, la petición adquiere la identidad y sus roles. Esta identidad se propaga en las llamadas que se realizan desde el servlet a otros componentes.
En este caso, al acceder a la URL /holamundorestringido
se lanza la autenficación y se comprueba que el usuario tiene el rol usuario-saludo
para permitirle el acceso al servlet.
2.3.4. Control de acceso basado en roles
En la arquitectura Enterprise JavaBeans, la identidad de seguridad se representa por un objeto java.security.Principal
. Este objeto actúa como representante de usuarios, grupos, organizaciones, tarjetas inteligentes, etc. frente a la arquitectura de control de acceso de los enterprise beans.
Los descriptores del despliegue y las anotaciones en Java EE 5.0 incluyen elementos que declaran a qué roles lógicos se permite el acceso a los métodos de los enterprise beans. Estos roles se consideran lógicos porque no reflejan directamente usuarios, grupos o cualquier otra identidad de seguridad en un entorno operacional específico. Los roles se hacen corresponder con grupos de usuarios o usuarios del mundo real cuando el bean se despliega. Esto permite que el bean sea portable ya que cada vez que el bean se despliega en un nuevo sistema, los roles se asignan a usuarios y grupos específicos de ese entorno operacional.
Definición de roles con anotaciones
Para declarar los roles con permiso de acceso a los métodos de un bean primero se utiliza la anotación @DeclareRoles
en la que se declaran todos los roles que accederán a distintos métodos del bean. Después podemos especificar para cada uno de los métodos qué roles están autorizados con la anotación @RolesAllowed
. Si ponemos esta anotación a nivel de clase, se aplica a todos los métodos de la clase.
Por ejemplo, en el siguiente código se declaran los roles Admin
, Bibliotecario
y Socio
y se definen los permisos de acceso a los métodos.
@Stateless
@DeclareRoles({"Admin","Bibliotecario","Socio"})
public class OperacionBOBean implements OperacionBOLocal {
// ...
@RolesAllowed("Admin")
public String borraOperacion(String idOperacion) {
// ...
}
@RolesAllowed({"Admin","Bibliotecario"})
public String realizaReserva(String idUsuario, String idLibro) {
// ...
}
@RolesAllowed({"Admin","Bibliotecario"})
public String realizaPrestamo(String login, String isbn) { }
// ...
@PermitAll
public List<OperacionTO> listadoTodosLibros() { }
}
La anotación @PermitAll
declara que todos los roles tienen acceso al método.
2.3.5. La identidad de seguridad runAs
Mientras que las anotaciones @RolesAllowed
y los elementos method-permission
especifican qué roles tienen acceso a qué métodos del bean, la anotación @RunAs
y el elemento security-identity
especifica bajo qué rol se ejecuta el bean, usando el elemento runAs
. En otras palabras, el objeto rol que se define en runAs
se usa como la identidad del enterprise bean cuando éste intenta invocar métodos en otros beans. Esta identidad no tiene por qué ser la misma que la identidad que accede al bean por primera vez.
Por ejemplo, la siguiente anotación declara que todos los métodos del bean EmployeeService
siempre se van a ejecutar con la identidad admin
.
@RunAs("admin")
@Stateless public class EmployeeServiceBean implements EmployeeService {
...
}
Esta clase de configuración es útil cuando el enterprise bean o los recursos accedidos en el cuerpo del método requieren un rol distinto del que ha sido usado para obtener acceso al método. Por ejemplo, el método create()
podría llamar a un método en el enterprise bean X que requiera la identidad de seguridad de Administrador
.
Podemos entender la secuencia de cambio de identidades de la siguiente forma:
-
El cliente invoca un método del bean con una identidad
Id1
. -
El bean comprueba si la identidad
Id1
tiene permiso para ejecutar el método. La tiene. -
El bean consulta el elemento
security-identity
y cambia la identidad a la que indica ese elemento. Supongamos que es la identidadId2
. -
El bean realiza las llamadas dentro del método con la identidad
Id2
.
Es obligado resaltar que hay que usar con precaución esta funcionalidad, ya que con ella podemos atribuir cualquier rol a cualquier usuario.
Una limitación de la funcionalidad es que es obligado definir un único rol para todos los métodos.
2.3.6. Gestión de seguridad programativa en el enterprise bean
En el código del enterprise bean es posible comprobar qué usuario o grupo ha realizado la llamada al bean y si tiene un determinado rol asociado. Para ello se usan los siguientes métodos del SessionContext
:
-
Principal getCallerPrincipal()
: devuelve el objetoPrincipal
asociado al usuario o grupo que ha llamado al método. -
Boolean isCallerInRole(String rol)
: devuelvetrue
ofalse
dependiendo de si el usuario o grupo que ha llamado al método pertenece al rol que se le pasa como parámetro.
El objeto SessionContext
se obtiene declarando el método setSessionContext(SessionContext ctx)
del ciclo de vida del bean y guardando el contexto en una variable de instancia del bean. El contenedor EJB llamará a este método en el momento de creación del enterprise bean.
Un ejemplo de código en el que se utilizan estos métodos:
@Stateless
public class MiBean implements SessionBean {
@Resource
SessionContext ctx;
...
public void miMetodo() {
System.out.println(ctx.getCallerPrincipal().getName());
if (ctx.isCallerInRole("administrador")) {
//código ejecutado por administradores
System.out.println("Me llama un administrador");
}
if (ctx.isCallerInRole("bibliotecario")){
//código ejecutado por bibliotecarios
System.out.println("Me llama un bibliotecario");
}
}
...
2.4. Ejercicios
2.4.1. (0,75 puntos) Ciclo de vidad de bean con estado
-
Comprueba el funcionamiento de un bean con estado creando una versión con estado del bean Calculadora llamada
CalculadoraConEstado
. Escribe un servlet que compruebe su uso accesible desde la página web principal y un test Arquillian. -
Comprueba el funcionamiento del ciclo de vida del bean con estado añadiendo métodos con las anotaciones del ciclo de vida que escriban algún mensaje por la salida estándar. Comprueba que el funcionamiento del ciclo de vida es el indicado en los apuntes. Escribe la explicación en un fichero
respuestas.txt
en la raíz del repositorio.
2.4.2. (0,75 puntos) Acceso seguro a un método
En esta sesión debes probar el registro de usuarios para que puedan acceder al método seguro SaludaRestringido
del enterprise bean SaludoRestringidoServicio
.
-
Usando el comando
add-user.sh
añade en el servidor un usuario con el rol (grupo)usuario-servicio
. -
Configura correctamente la seguridad en el
web.xml
y en el bean para que el acceso al servletHolaMundoRestringidoServlet
y al bean esté limitado sólo a un usuario con este rol. -
Escribe otro bean llamado
SaludoRestringidoProgServicio
en el que pruebes la seguridad programativa. -
Comprueba el correcto funcionamiento y escribe los resultados en el fichero
respuestas.txt
.