Filtering JAX-RS Entities with Standard Security Annotations

Few times I’ve ran into a situation in which I wanted to return only a part of entity I was working with, in my service, to the client-side. Feature that would allow me to send this partial set of fields should not only be driven by my own filtering rules but it also should take into consideration application roles the current user is in. It’s not an uncommon use-case I’d say so in Jersey we’d come up with the Entity Filtering concept that would allow you to do such a thing: sending subgraph of entities from server to client (and vice versa), define your own filtering rules and make sure users get only the fields they are supposed to see.

You can, of course, define role-based access rules to JAX-RS resources to protect particular method, return specific DTO or filter the entity manually directly in the method. Both Jersey 1 and Jersey 2 allow you to make sure that particular resource method is being invoked only by users in certain user roles. There are multiple approaches to achieve this including annotating your resources and resource methods with standard security annotations from javax.annotation.security package (see Authorization – securing resources for example). However, for entities there wasn’t such an easy and straightforward approach to select and return only a part of entity.

Gerard covered the general concepts of Entity Filtering in his Selecting level of detail returned by varying the content type (part I, part II) articles and in my article I’d like to focus on filtering entities based on standard security annotations from javax.annotation.security package.

Examples showing these concepts are available in Jersey workspace (run them with `mvn compile exec:java`):

Idea

Support for Entity Filtering in Jersey introduces a convenient facility for reducing the amount of data exchanged over the wire between client and server without a need to create specialized data view components. The main idea behind this feature is to give you APIs that will let you to selectively filter out any non-relevant data from the marshalled object model before sending the data to the other party based on the context of the particular message exchange. This way, only the necessary or relevant portion of the data is transferred over the network with each client request or server response, without a need to create special facade models for transferring these limited subsets of the model data.

Entity Filtering feature allows you to define your own entity-filtering rules for your entity classes based on the current context (e.g. matched resource method) and keep these rules in one place (directly in your domain model). It is also possible to assign security access rules to entity classes properties and property accessors.

Role-based Entity Filtering

Filtering the content sent to the client (or server) based on the authorized security roles is a commonly required use case. By registering SecurityEntityFilteringFeature you can leverage the Jersey Entity Filtering facility in connection with standard javax.annotation.security annotations plus you can define access rules to the resource methods. Supported security annotations are:

Let’s assume we have a simple project-tracking JAX-RS application consisting of a few classes in domain model and a few resources. Back-end sends JSON messages (containing entities) to front-end and front-end displays web-pages with this data accordingly. Important thing is that you don’t need all the data from the server to correctly display pages to the users. Similarly you don’t want to send all the existing data connected with the entity to the users that are not supposed to see them. We define three user roles in our application: user, manager and client.

I’ll illustrate the concept on the Project entity from the domain model:

public class Project {

    public Long getId() { ... }

    public String getName() { ... }

    public String getDescription() { ... }

    public String getSecret() { ... }

    public List<Task> getTasks() { ... }

    public List<User> getUsers() { ... }

    public List<Report> getReports() { ... }

    // fields and setters
}

By defining the following access rules:

  • secret is not visible to anyone and won’t be sent to the other party (it’s a custom back-end property)
  • tasks are visible to managers and users
  • users are visible only to managers
  • reports are visible to managers and clients
  • other fields (id, name, description) are visible to anyone

We have a clear image how the annotated Project entity should look like:

public class Project {

    public Long getId() { ... }

    public String getName() { ... }

    public String getDescription() { ... }

    @DenyAll
    public String getSecret() { ... }

    @RolesAllowed({"manager", "user"}) 
    public List<Task> getTasks() { ... }

    @RolesAllowed({"manager"})
    public List<User> getUsers() { ... }

    @RolesAllowed({"manager", "client"})
    public List<Report> getReports() { ... }

    // fields and setters
}

Non-annotated properties or property accessors are present in the result “by default”. This means that id, name and description (not affected by any filtering annotation) will be returned every-time regardless the security role the current user is in. The processing of this fields is the same as if you put @PermitAll on the class or on the property accessors.

Based on the annotations the returned entity will look different for each user role:

  • manager will see id, name, description, tasks, users and reports
  • user will see id, name, description and tasks
  • client will see id, name, description and reports

Entity Filtering is transitive which means that you can define access rules on sub-entities (i.e. Reports) as well. Different user roles will get different results for the whole entity graph.

We do not need any other restrictions to be defined at the moment so let’s take a look how the Entity Filtering works on the server and on the client.

Server

Instead of creating one resource method per user-role (+ special DTO classes), creating various Reader/Writer interceptors or using another 3rd party library with Entity Filtering it’s enough to create a simple resource class like:

@Path("projects")
@Produces("application/json")
public class ProjectsResource {

    @POST
    @RolesAllowed("manager")
    public Project createProject(final Project project) {
        ...
    }

    @GET
    @Path("{id}")
    public Project getProject(@PathParam("id") final Long id) {
        ...
    }

    @GET
    public List<Project> getProjects() {
        ...
    }
}

Jersey will take care of filtering fields as defined by security annotations.

There is no need to annotate both resource method and domain entity with security annotations if you only want to restrict access to entity fields. Annotate only setters/getters of the entity and entity-filtering would work as expected even in this case.

Take a look at the resource one more time and you’ll see that only createProject is annotated with security annotation. This method is intended to be invoked only by users in role manager hence the annotation is needed to prevent unwanted calls from unprivileged users. Other two methods (getProject, getProjects) can be invoked by anyone yet entity-filtering rules on the returned entity will be still applied.

There are two mandatory steps needed to be done to make Entity Filtering work. The first one is to set the correct SecurityContext in your application. If you’re running your application in a web-container that allows you to configure security constraints to deployed applications (i.e. enable BASIC auth) and you configured them, the SecurityContext is set and you don’t need to do anything else. Otherwise you need to set it yourself in a request filter:

@Provider
@PreMatching
public class SecurityRequestFilter implements ContainerRequestFilter {

    @Override
    public void filter(final ContainerRequestContext requestContext)
                throws IOException {
        requestContext.setSecurityContext(new SecurityContext() {

            @Override
            public Principal getUserPrincipal() {
                ...
            }

            @Override
            public boolean isUserInRole(final String role) {
                ...
            }

            @Override
            public boolean isSecure() {
                ...
            }

            @Override
            public String getAuthenticationScheme() {
                ...
            }
        });
    }
}

The second step is to register SecurityEntityFilteringFeature:

@ApplicationPath("/")
public class MyApplication extends ResourceConfig {

    public MyApplication() {
        // Set entity-filtering scope via configuration.
        property(EntityFilteringFeature.ENTITY_FILTERING_SCOPE,
                    new Annotation[] {SecurityAnnotations.rolesAllowed("manager")});

        // Register the SecurityEntityFilteringFeature.
        register(SecurityEntityFilteringFeature.class);

        // Further configuration of ResourceConfig.
        register( ... );
    }
}

Now everything should be set and ready to use entity-filtering.

Other ways to pass entity-filtering user roles to Jersey

There are also other ways than annotating resources to tell Jersey which entity-filtering scopes (in this case user roles) should be used when processing an entity.

You can see one of them in MyApplication class above. It’s possible to set a property (EntityFilteringFeature.ENTITY_FILTERING_SCOPE) that defines entity-filtering scope (user role) for every request/response that is processed in the application (note: user still needs to be in roles defined by this property otherwise the role is ignored).

Note that values of entity-filtering scope property are annotations. Since it’s not very easy to create an instance of an annotation in Java a SecurityAnnotations helper class is available for you to create them when needed.

If you’d rather define “per-response” scope it is possible to provide entity-filtering scope annotations in the response, along with the entity:

@Path("projects")
@Produces("application/json")
public class ProjectsResource {

    @GET
    public Response getProjects() {
        return Response
                .ok()
                .entity(new GenericEntity<List<Project>>(projects) {},
                     new Annotation[]{SecurityAnnotations.rolesAllowed("manager")})
                .build();
    }
}

When the multiple approaches are combined, the priorities of calculating the applied scopes are as follows: Entity annotations in request or response > Property passed through Configuration > Annotations applied to a resource method or class.

(Managed) Client

Entity Filtering is not restricted to be used only on server (resources / resource methods) but it can also be used with JAX-RS clients or injected web targets. Similarly to the way we configured our JAX-RS application, we need to register SecurityEntityFilteringFeature in order to filter entities on client:

final ClientConfig config = new ClientConfig()
    // (optional) Set entity-filtering scope via configuration.
    .property(EntityFilteringFeature.ENTITY_FILTERING_SCOPE,
                  new Annotation[] {SecurityAnnotations.rolesAllowed("manager")})
    // Register the SecurityEntityFilteringFeature.
    .register(SecurityEntityFilteringFeature.class)
    // Further configuration of ClientConfig.
    .register( ... );

// Create new client.
final Client client = ClientClientBuilder.newClient(config);

// Use the client.

Obviously there is one big difference between clients and server. Clients cannot infer user roles from SecurityContext so you need to provide them in some other way. You can set either EntityFilteringFeature.ENTITY_FILTERING_SCOPE property in ClientConfig to apply entity-filtering scope to all incoming/outgoing entities (see example above) or you can set roles directly when creating requests:

// Use the client.
client.target(uri)
    .request()
    .post(Entity.entity(project,
             new Annotation[] {SecurityAnnotations.rolesAllowed("user")}));

The project entity instance will contain all fields allowed for user security role when sent to the other party.

Besides the manually created client instances Jersey allows you to use injected WebTarget instances in your resources (as described in Managed JAX-RS Client) and you can use entity-filtering in them as well:

@Path("client")
@Produces("application/json")
public class ClientResource {

    @Uri("projects")
    private WebTarget target;

    @GET
    public List<Project> getProjects() {
        return target.request()
            .post(Entity.entity(project,
                      new Annotation[] {SecurityAnnotations.rolesAllowed("user")}));
    }
}

Limitations and Next Steps

Even though Entity Filtering is a universal feature and is not hard-wired with any media type or provider we currently support only JSON via MOXy. We understand that this is not enough and we plan to bring support also for JSON via Jackson and XML via MOXy. JSON support via Jackson (2.x) provider is available since Jersey 2.16.

If you feel that something is missing or not working as expected we’d be happy for any comments or suggestions in the discussion below or in our mailing list.

Examples

Further Reading