Skip to main content

Last week we published the post, “MicroProfile JWT” by @JLouisMonteiro that covers the basics, standards, pros, and cons involved in implementing stateless security with Microprofile JWT.

In this post, we are going to deep dive into JWT role-based access control on Apache TomEE. Since the release of TomEE 7.1, we now have a JWT specification implementation defined using Microprofile version 1.2. The specification is available in the official release.

Marking a JAX-RS Application as Requiring MP-JWT RBAC

For this, you need to add the annotation @LoginConfig to your existing javax.ws.rs.core.Application subclass. The purpose of the @LoginConfig is to annotate the JAX-RS Application as requiring MicroProfile JWT RBAC. This is the equivalent to the web.xml login-config element.

import javax.ws.rs.core.Application;
import org.eclipse.microprofile.auth.LoginConfig;

@LoginConfig(authMethod = "MP-JWT")
public class ApplicationConfig extends Application {
}

Accessing Claims

We can inject JsonWebToken to denote the currently authenticated caller with @RequestScoped scoping.

import javax.ws.rs.Path;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import org.eclipse.microprofile.jwt.JsonWebToken;

@Path("movies")
@ApplicationScoped
public class MovieAppResource {
  @Inject
  private JsonWebToken jwtPrincipal;
}

From the current JsonWebToken we can also have an injection of claims using the ClaimValue interface.

import javax.ws.rs.Path;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import org.eclipse.microprofile.jwt.Claim;
import org.eclipse.microprofile.jwt.ClaimValue;

@Path("movies")
@ApplicationScoped
public class MovieAppResource {
  @Inject
  @Claim("email")
  private ClaimValue email;
}

Notice that in the previous example, the MovieAppResource has an ApplicationScope, that’s why we need to have a claim injected using the ClaimValue type. Another way to extend the JWT propagation is by creating a RequestScoped class and use an instance of that class as an attribute of the ApplicationScope resource. The end result provides a cleaner code and better encapsulation.

import javax.ws.rs.Path;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

@Path("movies")
@ApplicationScoped
public class MovieAppResource {
  @Inject
  private Person person;
}

import org.eclipse.microprofile.jwt.Claim;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import org.eclipse.microprofile.jwt.Claim;

@RequestScoped
public class Person {
  @Inject
  @Claim("email")
  private String email;

  public Person() {
  }

  public String getEmail() {
    return email;
  }
}

Following previous JWT propagation approach, we can also map enum attributes for the Person class:

public enum Language {
   SPANISH,
   ENGLISH;
}

@RequestScoped
public class Person {
  @Inject
  @Claim("language")
  private Language language;

  public Person() {
  }

  public Language getLanguage() {
    return language;
  }
}

JAX-RS Container API Integration

A JAX-RS Security method like isUserInRole from SecurityContext returns true for any name that is included in the MP-JWT "groups" claim, as well as for any role name that has been mapped to a group name in the MP-JWT "groups" claim. The getUserPrincipal() method returns an instance of JsonWebToken we previously described.

import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;
import javax.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.jwt.JsonWebToken;

@Path("movies")
@ApplicationScoped
public class MovieAppResource {
  @Context
  private SecurityContext securityContext;
 
  if (!securityContext.isUserInRole("admin")) { //Do fun operations }

  JsonWebToken jwt = (JsonWebToken) securityContext.getUserPrincipal();
}

The MP-JWT supports the expected annotations RolesAllowed, PermitAll and DenyAll described in the JSR-250.

As defined in the MP-JWT specification, any role names used in @RolesAllowed, or equivalent security constraint metadata, that match names in the role names are part of the MP-JWT "groups" claim. The groups of claims is an abstraction base upon the fact that the JWT payload is not limited to store only claims related with roles.

The @RolesAllowed annotation still allows authorization decision wherever the security constraint has been applied.

import javax.ws.rs.Path;
import javax.ws.rs.PUT;
import javax.ws.rs.DELETE;
import javax.ws.rs.Consumes;
import javax.ws.rs.PathParam;
import javax.annotation.security.RolesAllowed;
import javax.enterprise.context.ApplicationScoped;

@Path("movies")
@ApplicationScoped
public class MovieAppResource {
  @PUT
  @Path("{id}")
  @Consumes("application/json")
  @RolesAllowed("update")
  public Movie method( @PathParam("id") long id, Movie movie) {
    //Do fun operations
    return movie;
  }

  @DELETE
  @Path("{id}")
  @RolesAllowed("delete")
  public void deleteMovie(@PathParam("id") long id) {
    //Do fun operations
    service.deleteMovie(id);
  }
}

Public Key Configuration

Verification of JSON Web Tokens (JWT) passed to the Microservice in HTTP requests at runtime is done with the RSA Public Key corresponding to the RSA Private Key held by the JWT Issuer.

MicroProfile JWT leverages the MicroProfile Config specification to provide a consistent means of passing all supported configuration options via system properties. Any vendor-specific methods of configuration are still valid and shall be considered to override any standard configuration mechanisms.

Here we see the TomEE flavor which provides a runtime alternative for configuring the RSA public key for verification and offers additional settings to configure a window of tolerance for the JWT’s exp claim.

import org.apache.tomee.microprofile.jwt.config.JWTAuthContextInfo;
import org.superbiz.moviefun.utils.TokenUtil;

import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;
import java.security.KeyFactory;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Optional;

@Dependent
public class MoviesMPJWTConfigurationProvider {

   public static final String ISSUED_BY = "/oauth2/token";

   @Produces
   Optional getOptionalContextInfo() throws Exception {
       JWTAuthContextInfo contextInfo = new JWTAuthContextInfo();

       // todo use MP Config to load the configuration
       contextInfo.setIssuedBy(ISSUED_BY);

       byte[] encodedBytes = TokenUtil.readPublicKey("/publicKey.pem").getEncoded();

       final X509EncodedKeySpec spec = new X509EncodedKeySpec(encodedBytes);
       final KeyFactory kf = KeyFactory.getInstance("RSA");
       final RSAPublicKey pk = (RSAPublicKey) kf.generatePublic(spec);

       contextInfo.setSignerKey(pk);
       contextInfo.setExpGracePeriodSecs(10);

       return Optional.of(contextInfo);
   }

   @Produces
   JWTAuthContextInfo getContextInfo() throws Exception {
       return getOptionalContextInfo().get();
   }
}

You can check an entire application making usage of TomEE 7.1 with JWT support here:
https://github.com/tomitribe/microservice-with-jwt-and-microprofile.git

 

Ivan Junckes Filho

Ivan joins the Tomitribe Product Team with 8+ years of software development experience, including several years at IBM. His core skills include Java EE, REST, SOA, JPA, CDI, JMS, NoSQL databases, Applications Servers such as Apache TomEE and JBoss, Agile plus other Java related technologies.

Cesar Hernandez

Cesar Hernandez

César Hernández is a Senior Software Engineer at Tomitribe with experience in Enterprise Java Applications. He is a Java Champion, Duke's Choice Award winner, Eclipse Committer, Open Source advocate, teacher, and public speaker. When César is away from a computer, he enjoys spending time with his family, traveling and playing music with the Java Community Band, The Null Pointers. Follow Cesar on twitter @CesarHgt
CesarHgt

4 Comments

  • Guillermo says:

    Hi, I did a similar approach but with open liberty, but I have some doubts and I don’t know if I’m missing something. at openliberty in the server config, I use to add jwt tag config with the public key or secret key, in this case for tomee I didn’t see something similar.

    You can see what I meaning here https://github.com/gdiazs/microprofile-demo/blob/master/microservices/accounts/accounts-web/src/main/resources/liberty/server.xml#L13 line

    the life cycle of this CDI config bean MoviesMPJWTConfigurationProvider ? Who calls who in terms of JEE continer?

    MoviesMPJWTConfigurationProvider It is mandatory? If I want to add a JNDI jdbc or JMS?

    how can achieve with microprofile with tomee embedded?

  • Thanks. We are a current startup with a education enterprise system with a large amount of users and user specific metrics. Is RBAC with jwt the way forward.Since currently we don’t know what types of roles we are going to have In future. We might have a role for each academic institution. Would the current system suffice or should we add say an “institution claim” Would an institution be a user or a group?

    • Cesar Hernandez says:

      Thank you for reaching out.
      RBAC has been around the industry for quite some time, MicroProfile JWT brings to Java Enterprise the stateless RBAC mechanisms with interoperability in mind.
      For the case you describe, in the JWT role, you can describe your Authorization and having an Institution as a JWT Claim sounds like a good mechanism to provide JWT isolation amount Institutions.

      Don’t miss to check out our Tribestream API Gateway training material https://tribestream.io/learning-journey/ and product information in case you want to know more about JWT RBAC architecture.

Leave a Reply