You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomee.apache.org by David Blevins <da...@gmail.com> on 2019/09/09 17:56:33 UTC

Rewrite of TOMEE-2517 Bean Validation with MicroProfile JWT (was Re: TOMEE-2515 MicroProfile JWT 1.1 Cleanup)

> On May 13, 2019, at 11:47 AM, David Blevins <da...@gmail.com> wrote:
> 
> - TOMEE-2517: MP-JWT and BeanValidation adds a fancy new feature that allows users to use Bean Validation to check JWTs.  You simply write a validation constraints for against the JsonWebToken and annotate your method.  A method no longer needs to use @RolesAllowed and can be very expressive and specific through the power of bean validation.
> 
> - TOMEE-2517: MP-JWT and BeanValidation Example.  Any new feature needs documentation or it doesn't exist.  The example is functional and clean.  The README is barely there and will need more work.

Ok, TOMEE-2517 has been rewritten and merged.  All tests passed on my machine, we'll see what the CI says.

The heart of this feature is that it lets you replace uses of `@RolesAllowed` with annotations of your own creation.  Simply create Bean Validation annotations that validate against the MicroProfile JWT `JsonWebToken` interface.

A short example, where you previously had this:

    @Path("/movies")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    @RequestScoped
    public class MovieService {
    
        private Map<Integer, Movie> store = new ConcurrentHashMap<>();
    
        @GET
        @RolesAllowed({"manager", "user"})
        public List<Movie> getAllMovies() {
            return new ArrayList<>(store.values());
        }
    
        @POST
        @RolesAllowed("manager")
        public void addMovie(Movie newMovie) {
            store.put(newMovie.getId(), newMovie);
        }
    }

You can now do:

    @Path("/movies")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    @RequestScoped
    public class MovieService {
    
        private Map<Integer, Movie> store = new ConcurrentHashMap<>();
    
        @GET
        @User
        @Manager
        public List<Movie> getAllMovies() {
            return new ArrayList<>(store.values());
        }
    
        @POST
        @Manager
        public void addMovie(Movie newMovie) {
            store.put(newMovie.getId(), newMovie);
        }
    }

Of course, you can now start to validate other parts of the JWT besides `groups`, such as `aud` or `iss`:

    @Path("/movies")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    @RequestScoped
    public class MovieService {
    
        private Map<Integer, Movie> store = new ConcurrentHashMap<>();
    
        @GET
        @Issuer("https://movies.example.com")
        @User
        @Manager
        public List<Movie> getAllMovies() {
            return new ArrayList<>(store.values());
        }
    
        @POST
        @Audience("movies")
        @Manager
        public void addMovie(Movie newMovie) {
            store.put(newMovie.getId(), newMovie);
        }
    }


To make this work under the covers, we have to basically split a developer's bean class into two parts which are used by the different code that needs to validate.

So if you have a class called Foo that uses Bean Validation constraints for both JWT and Return value, you will get the following two classes generated:

 - `Foo$$ReturnConstraints` used by an updated version of `BValInterceptor`, which is a CDI interceptor for return and parameter validation
 - `Foo$$JwtConstraints` used by `ValidationInterceptor`, which is a JAX-RS `ContainerRequestFilter` that does the JWT validation

To determine if we need to do any of this for a particular class, we have `ClassValidationData`. If it looks as there are JWT constraints on the class, we use `ClassValidationGenerator` to handle the generation, which in turn delegates to these classes to do the ASM work:

 - `JwtValidationGenerator`
 - `ReturnValidationGenerator`

Effectively we're sorting your Bean Validation annotations into groups based on what they validate.

Overall, I consider this feature a pre-standard prototype.  The goal is to:

 - show it to as many people as we can
 - get people to see the appeal
 - get everyone thinking about what missing pieces might have to be created if we were to do it cleanly in a spec

There would likely need to be both Bean Validation and MicroProfile JWT specification updates for this.

What the Bean Validation spec improvements might be is an open question.  There are a few ways we could approach it.


-David