Corrigiendo la especificación microprofile jwt

2021/05/03

A continuación se detallan algunas de las pruebas que estuve haciendo hace unos meses con la especificación microprofile jwt propagation 1.2, también llamada JWT RBAC (role based access control) y que me llevaron a corregir la especificación.

Notas:

  1. Estas pruebas fueron realizadas con wildfly 22.0.1.Final.
  2. Las diferentes versiones del código mostrado aquí se encuentra en el repositorio https://git.sr.ht/~jumapico/jakarta-ee-experiments, bajo el directorio mp4-demo-jwt.

Versión 01

Se comienza con:

  1. Un pom.xml que contiene las dependencias de microprofile:

    <dependency>
        <groupId>org.eclipse.microprofile</groupId>
        <artifactId>microprofile</artifactId>
        <version>4.0.1</version>
        <type>pom</type>
        <scope>provided</scope>
    </dependency>
    
  2. La clase JAXRSActivator.java que activa jax-rs:

    @ApplicationPath("/")
    public class JAXRSActivator extends Application {}
    
  3. Una clase Resources.java que contendrá un resource que devuelve la información del token jwt:

    @Inject JsonWebToken jsonWebToken;
    
    @Path("info")
    @GET
    public String info() {
      return "Token info: " + jsonWebToken;
    }
    

    y otro que no permita el acceso, para poder probar que el control de acceso por roles funciona:

    @Path("denied")
    @GET
    @DenyAll
    public String denied() {
      return "This endpoint is denied";
    }
    
  4. El archivo beans.xml para la busqueda de beans

  5. El archivo jboss-web.xml para definir el path a utilizar por la aplicación:

    <context-root>/demojwt</context-root>
    

Se genera el war de la aplicación utilizando ./mvnw clean package y al deployar se obtiene la siguiente traza:

01:44:38,539 INFO  [org.jboss.as.repository] (DeploymentScanner-threads - 1) WFLYDR0001: Content added at location /home/jmpc/workspace/wildfly-22.0.1.Final/standalone/data/content/84/5c625078d3dbb1a5127f7e5d93a5ab6b650b36/content
01:44:38,575 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-6) WFLYSRV0027: Starting deployment of "demo-jwt-1.0-SNAPSHOT.war" (runtime-name: "demo-jwt-1.0-SNAPSHOT.war")
01:44:39,206 INFO  [org.jboss.weld.deployer] (MSC service thread 1-2) WFLYWELD0003: Processing weld deployment demo-jwt-1.0-SNAPSHOT.war
01:44:39,393 INFO  [org.hibernate.validator.internal.util.Version] (MSC service thread 1-2) HV000001: Hibernate Validator 6.0.22.Final
01:44:39,808 INFO  [io.jaegertracing.internal.JaegerTracer] (MSC service thread 1-2) No shutdown hook registered: Please call close() manually on application shutdown.
01:44:39,909 INFO  [org.jboss.weld.Version] (MSC service thread 1-8) WELD-000900: 3.1.5 (Final)
01:44:40,333 INFO  [org.infinispan.CONTAINER] (ServerService Thread Pool -- 78) ISPN000128: Infinispan version: Infinispan 'Corona Extra' 11.0.8.Final
01:44:40,430 INFO  [org.jboss.weld.Bootstrap] (Weld Thread Pool -- 2) WELD-000119: Not generating any bean definitions from uy.jumapico.experiments.demojwt.boundary.Resources because of underlying class loading error: Type org.eclipse.microprofile.jwt.JsonWebToken from [Module "deployment.demo-jwt-1.0-SNAPSHOT.war" from Service Module Loader] not found.  If this is unexpected, enable DEBUG logging to see the full error.
01:44:40,446 INFO  [org.infinispan.CONFIG] (MSC service thread 1-4) ISPN000152: Passivation configured without an eviction policy being selected. Only manually evicted entities will be passivated.
01:44:40,448 INFO  [org.infinispan.CONFIG] (MSC service thread 1-4) ISPN000152: Passivation configured without an eviction policy being selected. Only manually evicted entities will be passivated.
01:44:40,564 INFO  [org.infinispan.PERSISTENCE] (ServerService Thread Pool -- 78) ISPN000556: Starting user marshaller 'org.wildfly.clustering.infinispan.spi.marshalling.InfinispanProtoStreamMarshaller'
01:44:40,944 INFO  [org.jboss.as.clustering.infinispan] (ServerService Thread Pool -- 78) WFLYCLINF0002: Started http-remoting-connector cache from ejb container
01:44:41,715 INFO  [org.jboss.resteasy.resteasy_jaxrs.i18n] (ServerService Thread Pool -- 78) RESTEASY002225: Deploying javax.ws.rs.core.Application: class uy.jumapico.experiments.demojwt.boundary.JAXRSActivator$Proxy$_$$_WeldClientProxy
01:44:41,765 ERROR [org.jboss.msc.service.fail] (ServerService Thread Pool -- 78) MSC000001: Failed to start service jboss.deployment.unit."demo-jwt-1.0-SNAPSHOT.war".undertow-deployment: org.jboss.msc.service.StartException in service jboss.deployment.unit."demo-jwt-1.0-SNAPSHOT.war".undertow-deployment: java.lang.NoClassDefFoundError: Lorg/eclipse/microprofile/jwt/JsonWebToken;
	at org.wildfly.extension.undertow@22.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentService$1.run(UndertowDeploymentService.java:81)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at org.jboss.threads@2.4.0.Final//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
	at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1990)
	at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
	at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
	at java.base/java.lang.Thread.run(Thread.java:829)
	at org.jboss.threads@2.4.0.Final//org.jboss.threads.JBossThread.run(JBossThread.java:513)
Caused by: java.lang.NoClassDefFoundError: Lorg/eclipse/microprofile/jwt/JsonWebToken;
	at java.base/java.lang.Class.getDeclaredFields0(Native Method)
	at java.base/java.lang.Class.privateGetDeclaredFields(Class.java:3061)
	at java.base/java.lang.Class.getDeclaredFields(Class.java:2248)
	at org.jboss.resteasy.resteasy-jaxrs@3.14.0.Final//org.jboss.resteasy.spi.metadata.ResourceBuilder.processDeclaredFields(ResourceBuilder.java:1008)
	at org.jboss.resteasy.resteasy-jaxrs@3.14.0.Final//org.jboss.resteasy.spi.metadata.ResourceBuilder.processFields(ResourceBuilder.java:987)
	at org.jboss.resteasy.resteasy-jaxrs@3.14.0.Final//org.jboss.resteasy.spi.metadata.ResourceBuilder.fromAnnotations(ResourceBuilder.java:869)
	at org.jboss.resteasy.resteasy-jaxrs@3.14.0.Final//org.jboss.resteasy.spi.metadata.ResourceBuilder.getRootResourceFromAnnotations(ResourceBuilder.java:830)
	at org.jboss.resteasy.resteasy-jaxrs@3.14.0.Final//org.jboss.resteasy.plugins.server.resourcefactory.POJOResourceFactory.<init>(POJOResourceFactory.java:38)
	at org.jboss.resteasy.resteasy-jaxrs@3.14.0.Final//org.jboss.resteasy.core.ResourceMethodRegistry.addPerRequestResource(ResourceMethodRegistry.java:81)
	at org.jboss.resteasy.resteasy-jaxrs@3.14.0.Final//org.jboss.resteasy.spi.ResteasyDeployment.registration(ResteasyDeployment.java:487)
	at org.jboss.resteasy.resteasy-jaxrs@3.14.0.Final//org.jboss.resteasy.spi.ResteasyDeployment.startInternal(ResteasyDeployment.java:288)
	at org.jboss.resteasy.resteasy-jaxrs@3.14.0.Final//org.jboss.resteasy.spi.ResteasyDeployment.start(ResteasyDeployment.java:93)
	at org.jboss.resteasy.resteasy-jaxrs@3.14.0.Final//org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.init(ServletContainerDispatcher.java:140)
	at org.jboss.resteasy.resteasy-jaxrs@3.14.0.Final//org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.init(HttpServletDispatcher.java:42)
	at io.undertow.servlet@2.2.4.Final//io.undertow.servlet.core.LifecyleInterceptorInvocation.proceed(LifecyleInterceptorInvocation.java:117)
	at org.wildfly.extension.undertow@22.0.1.Final//org.wildfly.extension.undertow.security.RunAsLifecycleInterceptor.init(RunAsLifecycleInterceptor.java:78)
	at io.undertow.servlet@2.2.4.Final//io.undertow.servlet.core.LifecyleInterceptorInvocation.proceed(LifecyleInterceptorInvocation.java:103)
	at io.undertow.servlet@2.2.4.Final//io.undertow.servlet.core.ManagedServlet$DefaultInstanceStrategy.start(ManagedServlet.java:305)
	at io.undertow.servlet@2.2.4.Final//io.undertow.servlet.core.ManagedServlet.createServlet(ManagedServlet.java:145)
	at io.undertow.servlet@2.2.4.Final//io.undertow.servlet.core.DeploymentManagerImpl$2.call(DeploymentManagerImpl.java:588)
	at io.undertow.servlet@2.2.4.Final//io.undertow.servlet.core.DeploymentManagerImpl$2.call(DeploymentManagerImpl.java:559)
	at io.undertow.servlet@2.2.4.Final//io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:42)
	at io.undertow.servlet@2.2.4.Final//io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
	at org.wildfly.extension.undertow@22.0.1.Final//org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105)
	at org.wildfly.extension.undertow@22.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
	at org.wildfly.extension.undertow@22.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
	at org.wildfly.extension.undertow@22.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
	at org.wildfly.extension.undertow@22.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
	at io.undertow.servlet@2.2.4.Final//io.undertow.servlet.core.DeploymentManagerImpl.start(DeploymentManagerImpl.java:601)
	at org.wildfly.extension.undertow@22.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentService.startContext(UndertowDeploymentService.java:97)
	at org.wildfly.extension.undertow@22.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentService$1.run(UndertowDeploymentService.java:78)
	... 8 more
Caused by: java.lang.ClassNotFoundException: org.eclipse.microprofile.jwt.JsonWebToken from [Module "deployment.demo-jwt-1.0-SNAPSHOT.war" from Service Module Loader]
	at org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:255)
	at org.jboss.modules.ConcurrentClassLoader.performLoadClassUnchecked(ConcurrentClassLoader.java:410)
	at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:398)
	at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:116)
	... 39 more

01:44:41,772 ERROR [org.jboss.as.controller.management-operation] (DeploymentScanner-threads - 1) WFLYCTL0013: Operation ("deploy") failed - address: ([("deployment" => "demo-jwt-1.0-SNAPSHOT.war")]) - failure description: {"WFLYCTL0080: Failed services" => {"jboss.deployment.unit.\"demo-jwt-1.0-SNAPSHOT.war\".undertow-deployment" => "java.lang.NoClassDefFoundError: Lorg/eclipse/microprofile/jwt/JsonWebToken;
    Caused by: java.lang.NoClassDefFoundError: Lorg/eclipse/microprofile/jwt/JsonWebToken;
    Caused by: java.lang.ClassNotFoundException: org.eclipse.microprofile.jwt.JsonWebToken from [Module \"deployment.demo-jwt-1.0-SNAPSHOT.war\" from Service Module Loader]"}}
01:44:41,911 INFO  [org.jboss.as.server] (DeploymentScanner-threads - 1) WFLYSRV0010: Deployed "demo-jwt-1.0-SNAPSHOT.war" (runtime-name : "demo-jwt-1.0-SNAPSHOT.war")
01:44:41,912 INFO  [org.jboss.as.controller] (DeploymentScanner-threads - 1) WFLYCTL0183: Service status report
WFLYCTL0186:   Services which failed to start:      service jboss.deployment.unit."demo-jwt-1.0-SNAPSHOT.war".undertow-deployment: java.lang.NoClassDefFoundError: Lorg/eclipse/microprofile/jwt/JsonWebToken;
WFLYCTL0448: 1 additional services are down due to their dependencies being missing or failed

Dejando de lado la traza horrible (siempre son muy verbosas en java), lo que extraña es que no se encuentra la clase org.eclipse.microprofile.jwt.JsonWebToken que debido a que wildfly cumple con microprofile jwt propagation 1.2 debería encontrarse.

Versión 02

Releyendo la especificación con más detenimiento, en la sección Marking a JAX-RS Application as Requiring MP-JWT Access Control se indica que debe utilizarse la clase LoginConfig para indicar que la aplicación requiere el uso de control de acceso jwt.

Aquí es donde se detecta una inconsistencia en la especificación, ya que en el texto se indica que se agregó la anotación org.eclipse.microprofile.jwt.LoginConfig, pero en el código se utiliza org.eclipse.microprofile.annotation.LoginConfig.

Para determinar cual de las dos es la correcta, se busca el nombre de la clase en el jar. Para determinar el jar, se busca en las dependencias:

$ ./mvnw dependency:tree
...
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ demo-jwt ---
[INFO] uy.jumapico.experiments:demo-jwt:war:1.0-SNAPSHOT
[INFO] \- org.eclipse.microprofile:microprofile:pom:4.0.1:provided
...
[INFO]    +- org.eclipse.microprofile.jwt:microprofile-jwt-auth-api:jar:1.2:provided
...

De lo anterior, la clase es probable que se encuentre en el artefacto org.eclipse.microprofile.jwt:microprofile-jwt-auth-api:jar:1.2 y se la busca en el repositorio local de maven:

$ unzip -l $(find $HOME/.m2 -name 'microprofile-jwt-auth-api-1.2.jar') | grep LoginConfig
      578  2020-12-04 16:40   org/eclipse/microprofile/auth/LoginConfig.class

De lo anterior se encuentra que ninguno de los paquetes indicados en la especificación es correcto, sinó que el correcto es org.eclipse.microprofile.auth.

De lo anterior se modifica la clase JAXRSActivator, quedando como:

package uy.jumapico.experiments.demojwt.boundary;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/")
public class JAXRSActivator extends Application {}

y esta vez al realizar el deploy este no tiene problemas:

02:22:12,215 INFO  [org.jboss.as.server] (DeploymentScanner-threads - 2) WFLYSRV0010: Deployed "demo-jwt-1.0-SNAPSHOT.war" (runtime-name : "demo-jwt-1.0-SNAPSHOT.war")

Por supuesto aunque se realizó correctamente el deploy, la aplicación fallará al momento de realizarse un request ya que no se realizó la configuración completa de jwt.

Haciendo el request en una consola

$ curl 'http://localhost:8080/demojwt/info'

Se obtiene en el servidor el error (y una traza que se omite):

02:23:47,292 ERROR [io.undertow.request] (default task-1) UT005023: Exception handling request to /demojwt/info: java.lang.IllegalStateException: JWTAuthContextInfo has not been initialized. Please make sure that either 'mp.jwt.verify.publickey' or 'mp.jwt.verify.publickey.location' properties are set.

Versión 03

En lo que sigue se realizará la configuración para verificar la firma del token jwt. Como se indica en la sección Signature Verification Configuration Parameters.

En mi caso las pruebas las realicé utilizando tokens obtenidos por un Identity Server de WSO2. En lugar de utilizar la clave pública del certificado con el cual el WSO2 firma el certificado,

Para probar de forma rápida, se utilizará como clave pública el certificado utilizado en la especificación. Esto impedirá que sea posible utilizar un token firmado, pero sirve para asegurarse que se agrega la configuración mínima y la aplicación funciona.

Se agregan en /META-INF/ los archivos:

  1. microprofile-config.properties con la configuración de jwt propagation:

    mp.jwt.verify.publickey.location=/META-INF/public.pem
    
  2. public.pem con la clave pública que se encuentra en la especificación.

Se deploya la aplicación con los cambios y ahora si se obtiene el resultado esperado:

$ curl -v 'http://localhost:8080/demojwt/info'
> GET /demojwt/info HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Connection: keep-alive
< Content-Type: application/octet-stream
< Content-Length: 62
< Date: Mon, 03 May 2021 06:06:48 GMT
<
* Connection #0 to host localhost left intact
Token info: io.smallrye.jwt.auth.cdi.NullJsonWebToken@5940b3dd

$ curl -v -H 'Authorization: Bearer 123' 'http://localhost:8080/demojwt/info'
> GET /demojwt/info HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.74.0
> Accept: */*
> Authorization: Bearer 123
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 401 Unauthorized
< Connection: keep-alive
< Content-Type: text/html;charset=UTF-8
< Content-Length: 71
< Date: Mon, 03 May 2021 06:06:05 GMT
<
* Connection #0 to host localhost left intact
<html><head><title>Error</title></head><body>Unauthorized</body></html>

$ curl -v 'http://localhost:8080/demojwt/denied'
> GET /demojwt/denied HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Connection: keep-alive
< Content-Type: application/octet-stream
< Content-Length: 23
< Date: Mon, 03 May 2021 06:24:05 GMT
<
* Connection #0 to host localhost left intact
This endpoint is denied

Y claramente aquí ocurre un problema, ya que se nos debería haber negado el acceso al endpoint.

Versión 04

De la documentación de resteasy, capítulo Securing JAX-RS and RESTEasy se indica que es necesario activar la seguridad basada en roles, para que resteasy reconozca las anotaciones @RolesAllowed, @PermitAll y @DenyAll en los métodos de la resource class.

Según el capítulo Configuring RESTEasy de la misma documentación, alcanza con agregar en el archivo microprofile-config.properties la linea:

resteasy.role.based.security=true

para activar el uso de roles.

Deployando y volviendo a probar:

$ curl -v 'http://localhost:8080/demojwt/denied'
> GET /demojwt/denied HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Connection: keep-alive
< Content-Type: application/octet-stream
< Content-Length: 23
< Date: Mon, 03 May 2021 06:36:07 GMT
<
* Connection #0 to host localhost left intact
This endpoint is denied

La seguridad basada en roles no estaría funcionando….

Probando de deployar en la última versión de wildfly a la fecha, 23.0.2.Final, se ve que se cumple con lo indicado en la documentación:

$ curl -v 'http://localhost:8080/demojwt/denied'
> GET /demojwt/denied HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 403 Forbidden
< Connection: keep-alive
< Content-Type: text/html;charset=UTF-8
< Content-Length: 34
< Date: Mon, 03 May 2021 06:38:53 GMT
<
* Connection #0 to host localhost left intact
Access forbidden: role not allowed

Por lo que estamos ante la presencia de un bug en la versión 3.14.0.Final de resteasy (la utilizada por wildfly 22.0.1.Final).

Versión 05

En el caso de que sea necesario utilizar la versión 22.0.1.Final de wildfly, la solución al problema anterior es habilitar el control de acceso utilizando el archivo web.xml:

  1. Se agrega el archivo web.xml:

    <context-param>
        <param-name>resteasy.role.based.security</param-name>
        <param-value>true</param-value>
    </context-param>
    
  2. Se quita del pom.xml la linea

    <failOnMissingWebXml>false</failOnMissingWebXml>
    

Deployando nuevamente y probando acceder al endpoint:

$ curl -v 'http://localhost:8080/demojwt/denied'
> GET /demojwt/denied HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 403 Forbidden
< Connection: keep-alive
< Content-Type: text/html;charset=UTF-8
< Content-Length: 34
< Date: Mon, 03 May 2021 12:10:42 GMT
<
* Connection #0 to host localhost left intact
Access forbidden: role not allowed

Por supuesto, si se desea probar con un token que pueda ser validado conviene leer los pasos dados en microprofile-jwt/README.adoc del repositorio quickstart de wildfly para obtener un token firmado de prueba.

Cambio en la especificación

Como ya se mencionó al inicio, y se vió al realizar la Versión 02 del ejemplo, el paquete de LoginConfig no es correcto. Si bien la corrección es bien simple:

diff --git a/spec/src/main/asciidoc/interoperability.asciidoc b/spec/src/main/asciidoc/interoperability.asciidoc
index 7cc0ddf..cdc6275 100644
--- a/spec/src/main/asciidoc/interoperability.asciidoc
+++ b/spec/src/main/asciidoc/interoperability.asciidoc
@@ -380,13 +380,13 @@ for more information.

 ## Marking a JAX-RS Application as Requiring MP-JWT Access Control
 Since the MicroProfile does not specify a deployment format, and currently does
-not rely on servlet metadata descriptors, we have added an `org.eclipse.microprofile.jwt.LoginConfig`
+not rely on servlet metadata descriptors, we have added an `org.eclipse.microprofile.auth.LoginConfig`
 annotation that provides the same information as the web.xml login-config
 element. It's intended usage is to mark a JAX-RS `Application` as requiring
 MicroProfile JWT RBAC as shown in the following sample:

 ```java
-import org.eclipse.microprofile.annotation.LoginConfig;
+import org.eclipse.microprofile.auth.LoginConfig;

 import javax.ws.rs.ApplicationPath;
 import javax.ws.rs.core.Application;

para poder realizar contribuciones a los repositorios de microprofile hay que seguir las Contributing Guidelines, que implican aceptar el Eclipse Contributor Agreement (ECA) y al momento de realizar el commit firmarlo agregando la linea Signed-off-by:. Lo verificación de que el usuario cumple con las pautas de contribuciones se realizan de forma automática al realizar el pull request, mostrandose un error si el usuario no aceptó el ECA o no firmó el commit.

Conclusiones

  1. Aunque sin duda hubiera sido mucho más rápido descargar el repositorio quickstart de wildfly y copiar/pegar el ejemplo del directorio microprofile-jwt, vale más la pena leer la especificación y probar desde cero.
  2. Utilizar un editor que no agregue los import de las clases automáticamente ni autocomplete los métodos ayuda a pensar un poco más sobre el código. Obviamente al programar una aplicación completa el tiempo que se gana utilizando un IDE es enorme, pero durante una prueba de concepto pequeña vale la pena demorar un poco más y entender la aplicación de punta a punta.
  3. Me sorprendí gratamente al poder corregir la especificación de jwt aunque fuera un cambio menor y encontrar un bug en resteasy (habilitación de RBAC), aunque ya fuera solucionado en una versión más reciente. Eso último seguro que ayuda a ahorrar un tiempo importante de debug en desarrollo.