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.
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?
MoviesMPJWTConfigurationProvider was introduced in TomEE 7.1.0 to provides a runtime alternative for configuring the RSA public key.
Now in TomEE 8.0.0-M2, you also have the ability to configure the public key via microprofile-config.properties using the MP config spec. Check the following example: https://github.com/apache/tomee/tree/master/examples/mp-rest-jwt-public-key.
We are planning to release a new article describing TomEE-8.0.0-M2, MP 1.1.1 and MP config 1.3. stay tuned.
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?
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.