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:
- Estas pruebas fueron realizadas con wildfly 22.0.1.Final.
- 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:
-
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>
-
La clase
JAXRSActivator.java
que activa jax-rs:@ApplicationPath("/") public class JAXRSActivator extends Application {}
-
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"; }
-
El archivo
beans.xml
para la busqueda de beans -
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:
-
microprofile-config.properties
con la configuración de jwt propagation:mp.jwt.verify.publickey.location=/META-INF/public.pem
-
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
:
-
Se agrega el archivo
web.xml
:<context-param> <param-name>resteasy.role.based.security</param-name> <param-value>true</param-value> </context-param>
-
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
- 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.
- 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. - 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.