Este repositorio contiene diferentes ejemplos para la integración de la seguridad de aplicaciones basadas en Jakarta EE y Jakarta Security a través de OAuth2 y OpenID Connect.
En la demo utilizaremos Keycloak 24.x como IdP y Wildfly 36.x como servidor de aplicaciones compatible con Jakarta 10 y Jakarta Security 3.x.
Utilizaremos una implementación ad-hoc basada en Jakarta Security de los flujos de seguridad para controlar la autenticación y autorización sin utilizar los componentes específicos de cada servidor de aplicaciones de tal modo que la capa de seguridad esté desacoplada de la implementación de servidor.
Proyectos:
-
mcm-demo-lib-security-jwt: starter de seguridad para servicios REST basados en JWT basado en Jakarta Security.
-
mcm-demo-lib-security-oidc: starter de seguridad para aplicaciones web basado en OIDC basado en Jakarta Security.
-
mcm-demo-lib-user-client: cliente de la API REST de usuarios utilizado para mapear los usuarios y roles obtenidos de Keycloak con valores obtenidos a través de un servicio.
-
mcm-demo-api-customers: ejemplo de API REST Code-First con un CRUD básico de clientes.
-
mcm-demo-api-users: ejemplo de API REST Contract-First con consultas de usuarios.
-
mcm-demo-ui-jsf: frontal basado en JSF que consume la API de clientes y utiliza OIDC para la autenticación.
-
mcm-demo-ui-angular: frontal basado en Angular que consume la API de clientes y utiliza OIDC para la autenticación.
-
mcm-demo-ui-spring-boot: frontal basado en Spring Boot + Thymeleaf que consume la API de clientes y utiliza OIDC para la autenticación.
-
mcm-demo-consumer-customers-activation: consumidor de eventos de activación de clientes a a través de Kafka basado en Spring Cloud Stream.
En esta demo se utilizan dos librerías de seguridad, una para la autenticación basada en OIDC utilizada en la aplicación web y otra para la autenticación basada en JWT utilizada en la API REST.
Ambas librerías están basadas en Jakarta Security. En esta demo se utiliza la versión 3.0 de Jakarta Security aunque es compatible con Jakarta Security 1.0 simplemente actualizando los nombres de paquetes de javax.* a jakarta.*.
La implementación está basada en la interface HttpAuthenticationMechanism de Jakarta:
@ApplicationScoped
public class CustomAuthenticationMechanism implements HttpAuthenticationMechanism {
@Override
public AuthenticationStatus validateRequest(
HttpServletRequest request,
HttpServletResponse response,
HttpMessageContext context)
throws AuthenticationException {
// do some stuff
}
}
En el caso de la librería OIDC en primer lugar comprobaremos la sesión para verificar si el usuario está autenticado. En caso de que no lo esté, redirigiremos al usuario a la página de login de Keycloak enviando la URL de callback. En el caso de recibir el callback realizaremos una consulta a Keycloak a partir del código recibido para obtener el token de acceso y si este es válido crearemos la sesión del usuario.
En el caso de la librería JWT simplemente comprobaremos que este token viene correctamente informado en la cabecera de la petición. Validaremos la firla del token a partir de la clave pública de Keycloak obtenida desde el endpoint /realms/mcm-demo/protocol/openid-connect/certs y si es válido establecereos el contexto de seguridad con los roles obtenidos del token.
Antes de arrancar los proyectos es necesario configurar el Realm, los clientes y los usuarios. Crearemos tres usuarios de ejemplo con diferentes roles para probar la correcta autorización de los servicios.
En primer lugar crearemos una red local de Docker para que los contenedores puedan comunicarse entre ellos. Para ello utilizaremos el comando:
docker network create mcm-demo
Una vez creada la red arrancaremos el contenedor de Keycloak en el puerto 8090 con el siguiente comando:
docker run -d --name mcm-demo-keycloak --network mcm-demo -p 8090:8080 \
-e KEYCLOAK_ADMIN=admin \
-e KEYCLOAK_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak:24.0.3 \
start-dev
Una vez arrancado el contenedor podemos acceder a la consola de administración de Keycloak a través de la URL http://localhost:8090/ y autenticarnos con el usuario y contraseña configurados al crear el contendor (admin/admin).
Desde la consola crearemos el Realm mcm-demo:
Una vez creado el Realm crearemos el cliente "johndoe"
Una vez creado el cliente crearemos dos grupos de ejemplo:
Después crearemos un usuario de ejemplo johndoe@demo.com:
Y le asignaremos los dos roles creados anteriormente:
De tal modo que en el token JWT podamos ver los roles asignados al usuario:
La configuración de los usuarios creados deberá ser la siguiente:
Usuario | Roles |
---|---|
customer-manager |
customer-manager, customer-viewer |
customer-viewer |
customer-viewer |
johndoe |
user, admin |
Dado que los otros proyectos dependen de las librerías de seguridad, es necesario instalarlas en el repositorio local de Maven.
Para ello simplemente deberemos hacer ejecutar el comando ./mvnw clean install en los proyectos:
-
mcm-demo-lib-security-jwt
-
mcm-demo-lib-security-oidc
-
mcm-demo-lib-user-client
docker run -d \ --name mcm-demo-postgres \ --network mcm-demo \ -e POSTGRES_USER=user \ -e POSTGRES_PASSWORD=changeit \ -e POSTGRES_DB=mcmdemo \ -p 5432:5432 \ postgres:latest
Este proyecto expone una API REST con el CRUD básico de la entidad Customer guardando la información en una base de datos a través de JPA.
Para construir y arrancar en una imagen local el proyecto utilizaremos los comandos siguientes dentro del proyecto mcm-demo-api-customers:
./cmd-docker-create-image.sh ./cmd-docker-start.sh
El primero compila el proyecto y crea la imagen de Docker configurando la seguridad del perfil standalone-microprofile.xml de Wildfly.
El segundo comando arranca la imagen Docker con las variables de entorno necesarias para la conexión con Keycloak.
La información de la API se puede consultar a través del siguiente link.
Este proyecto consiste en un frontal básico basado en JSF que consume la API de clientes y utiliza OIDC para la autenticación propagando el token de acceso a la API.
Levantaremos el proyecto a través de Docker del mismo modo que la API de clientes:
./cmd-docker-create-image.sh ./cmd-docker-start.sh
El primer comando compila el proyecto y crea la imagen de Docker configurando la seguridad del perfil standalone.xml de Wildfly.
El segundo comando arranca la imagen Docker con las variables de entorno necesarias para la conexión con Keycloak y la API de clientes.
En este ejemplo en primer lugar definiremos una API REST de ejemplo que implementa un CRUD de la entidad Customer y posteriormente dos frontends basados tanto en JSF como en Spring Boot + Thymeleaf que se integran con dicha API.
Para las pruebas se ha utilizado la versión 36.0.1 de Wildfly, que se puede descargar desde https://wildfly.org/downloads/
Utilizaremos una configuración preestablecida para de standalone para el arranque en local en la que hemos añadidos los módulos de MicroProfile. Esta configuración se encuentra en la carpeta config.
./standalone.sh -c standalone-custom.xml
Una vez arrancado el servidor, podemos acceder a la consola de administración en:
Este proyecto está basado en Jakarta EE y define una API REST que implementa un CRUD de la entidad Customer.
En primer lugar arrancaremos la API ya que necesitaremos la definición OpenAPI de esta autogenerada a través de MicroProfile para construir el cliente de los frontends.
Para ello simplemente ejecutaremos el script cmd-docker-screate-image.sh que construye el proyecto y crea la imagen local de Docker.
Podemos ejecutar la imagen a través del scripts cmd-docker-start.sh que inicializa la red local de Docker y arranca la imagen.
Una vez arrancada la imagen comprobaremos que la API está correctamente desplegada consultando la definición de la API a través de:
curl --location 'http://127.0.0.1:8081/openapi'
Verificaremos que la API funciona obteniendo el listado de clientes que al no haber creado ninguno no debería devolver resultados:
curl --location 'http://127.0.0.1:8081/demo-api/api/customers'
Este servicio deberá devolver:
{
"content": [],
"page": 0,
"size": 10,
"totalElements": 0,
"totalPages": 0
}
La imagen docker de este proyecto ejecuta el perfil standalone-microprofile.xml de Wildfly ya que incluye los módulos que utilizaremos para generar dinámicamente la definición OpenAPI usando Code-First.
Este proyecto define un CRUD básico de clientes utilizando la API de consulta definida en el proyecto anterior. Durante la construcción del proyecto crea las clases del cliente a partir de la URL de nuestro servicio de modo que es necesario que esté en ejecución para compilar el proyecto.
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>7.12.0</version>
<executions>
<execution>
<id>generate-rest-client</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>http://127.0.0.1:8081/openapi</inputSpec>
<... />
</execution>
</executions>
</plugin>
La imagen docker de este proyecto ejecuta el perfil standalone.xml de Wildfly ya que es el que incluye los módulos de JSF utilizados por la interface web.
-
domain: entidades del dominio (no las entidades JPA) y servicios con la lógica del negocio.º
-
application: clases de aplicación que implementan la lógica del negocio y delegan en los servicios del dominio. Estas clases no deberían tener mucha lógica, sino que deberían delegar en los servicios del dominio.
-
infrastructure: clases de infraestructura que implementan la lógica de acceso a datos, como repositorios, adaptadores y servicios externos.
En primer lugar crearemos un clúster de Minikube con Docker como driver:
minikube start --driver=docker
Después iniciaremos la consola de administración de Minikube:
minikube dashboard
minikube stop
Prueba de lectura del token a través del grant type password para verificar la configuración del usuario:
curl -X POST \
-d "grant_type=password" \
-d "client_id=mcm-demo-client-jsf" \
-d "client_secret=HtsCdaR7o5KoNQpI0BOOWwtds1sxorCT" \
-d "username=johndoe" \
-d "password=changeit" \
"http://localhost:8090/realms/mcm-demo/protocol/openid-connect/token"