Hello MicroProfilers!
As some of you already know, we together with my Bulgarian JUG friend Dmitry are working on the MicroProfile hands-on-lab. At the moment it is targeted at MP 1.2, but we'll easily add 1.3 features once the specs are implemented.
The overview
The lab contains a sample microservice application as well as a step-by-step guide (which we just started writing). The app represents a magazine manager with articles, authors, subscribers, user management and HTML + jQuery UI. There are 5 microservices each of which running on a different runtime:
- user microservice runs on WildFly Swarm
- content microservice runs on IBM Liberty Profile
- subscribers microservice runs on Payara Micro
- authors microservice runs on Hammock
- GUI microservice runs on TomEE
The idea is that when you start with the lab you will have a fully functional app and you will be guided to add the different MP aspects (config, health, metrics, fault tolerance, JWT, etc.)
The JWT usecase
We wanted to showcase JWT in the following way:
- When a user logs in the web page, the GUI microservice performs a request to the user microservice
- The latter checks whether this user/password combination exists and if it does, it generates a JWT token and returns it
- The subsequent requests from the GUI set the Authorization header to the JWT token
- We use that to make sure that adding new articles in the content microservice is only possible if the caller belongs to the authors group. And adding a subscriber in the subscriber microservice is only available to users in the admin group
Unfortunately neither IBM (content) nor Payara (subscribers) seem to work.
The issues
In both microservices (in the JAX-RS resource more precisely) I inject the groups claim like that:
@Inject
@Claim(standard = Claims.groups)
private Set<String> roles;
However, IBM fails with a NullPointerException upon instantiating the resource class:
java.lang.NullPointerException
at com.ibm.ws.security.mp.jwt.cdi.JsonWebTokenBean.create(JsonWebTokenBean.java:72)
at [internal classes]
at org.jboss.weld.context.AbstractContext.get(AbstractContext.java:96)
at org.jboss.weld.bean.ContextualInstanceStrategy$DefaultContextualInstanceStrategy.get(ContextualInstanceStrategy.java:100)
at [internal classes]
at org.eclipse.microprofile.jwt.JsonWebToken$1561974719$Proxy$_$$_WeldClientProxy.getClaim(Unknown Source)
at com.ibm.ws.security.mp.jwt.cdi.ClaimProducer.getSetString(ClaimProducer.java:196)
at [internal classes]
at org.jboss.weld.context.AbstractContext.get(AbstractContext.java:96)
at org.jboss.weld.bean.ContextualInstanceStrategy$DefaultContextualInstanceStrategy.get(ContextualInstanceStrategy.java:100)
at [internal classes]
at bg.jug.microprofile.hol.content.ArticleResource$Proxy$_$$_WeldClientProxy.getAllArticles(Unknown Source)
Payara is not even able to deploy my app. It fails with:
org.jboss.weld.exceptions.DeploymentException: WELD-001408: Unsatisfied dependencies for type Set<String> with qualifiers @Claim
at injection point [BackedAnnotatedField] @Inject @Claim private bg.jug.microprofile.hol.subscribers.SubscribersResource.roles
at bg.jug.microprofile.hol.subscribers.SubscribersResource.roles(SubscribersResource.java:0)
at org.jboss.weld.bootstrap.Validator.validateInjectionPointForDeploymentProblems(Validator.java:362)
at org.jboss.weld.bootstrap.Validator.validateInjectionPoint(Validator.java:284)
at org.jboss.weld.bootstrap.Validator.validateGeneralBean(Validator.java:137)
at org.jboss.weld.bootstrap.Validator.validateRIBean(Validator.java:158)
at org.jboss.weld.bootstrap.Validator.validateBean(Validator.java:501)
at org.jboss.weld.bootstrap.Validator.validateBeans(Validator.java:487)
at org.jboss.weld.bootstrap.Validator.validateDeployment(Validator.java:462)
Here is the JWT that I send in the Authorization header:
{
"kid": "mp-hol",
"typ":"JWT",
"alg":"RS256"
}
{
"auth_time":1520753020,
"iss":"http:\/\/localhost:9100",
"groups":["admin"],
"exp":1520753320,
"given_name":"Bilbo",
"iat":1520753020,
"family_name":"Baggins"
}
It is signed with a private key on the users service (the one that generates it). And is of course crypted. Eventually, it looks like this:
eyJraWQiOiJtcC1ob2wiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJiaWxib0BleGFtcGxlLm9yZyIsInVwbiI6ImJpbGJvQGV4YW1wbGUub3JnIiwiYXV0aF90aW1lIjoxNTIwNzUzMzU2LCJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6OTEwMCIsImdyb3VwcyI6WyJhZG1pbiJdLCJleHAiOjE1MjA3NTM2NTYsImdpdmVuX25hbWUiOiJCaWxibyIsImlhdCI6MTUyMDc1MzM1NiwiZmFtaWx5X25hbWUiOiJCYWdnaW5zIn0.WcjsvYYsURSwthvzWNHqHVIa-N0pxkEsn-mOdfYo_pOLz9zxlr1NppARxe6TlKBBq7P-fAhuV_n8bJ_tNGerlkcMpEde12R25xYhWjSPja_T_7vT9HZrLH9sG0JNn_dfYLGgoe924PwkuQAhlYzak0aE7C2p0srYNMsRIUdDHlk
Of course both microservices contain the @LoginConfig annotation on their respective JAX-RS application class:
@LoginConfig(authMethod = "MP-JWT", realmName = "MP-HOL-JWT")
public class Application extends javax.ws.rs.core.Application {
}
I guess I am missing something, because my code was not much different than that of the TCK.
Here is the sample app in github:
Thank you for your help!
Ivan and Dmitry