gihan-maduranga
gihan-maduranga

Reputation: 4801

How to bypass path in servlet filter with Jersey security annotations in Java

I have implemented REST service using Jersey. To give more security, I have added jersey security annotation into REST method(@PermitAll, @DenyAll).

Below is my sample REST service:

@GET
@Path("/getall")
@Produces(MediaType.APPLICATION_JSON)
@PermitAll
public String getChartSupportedData(@QueryParam("items") int result) {
    // my code goes here
}

But the problem is that previously I have used javax.servlet.Filter filter to validate URI.

web.xml:

<filter>
    <filter-name>ApplicationFilter</filter-name>
    <filter-class>web.filter.ApplicationFilter</filter-class>
</filter>
<filter-mapping>
       <filter-name>ApplicationFilter</filter-name>
       <url-pattern>/rest/api/*</url-pattern>
       <dispatcher>REQUEST</dispatcher>
       <dispatcher>ASYNC</dispatcher>           
</filter-mapping>

According to access some REST services, HttpServletRequest should contain a valid token (generated by the application).

Some REST end points doesn't require a token to access the service. In that case, I have to bypass that in filter implementation:

private static String[] bypassPaths = { "/data/getall" };

So my requirement is something like that.

If we declared some REST end point as @PermitAll that path should not have declare in filter as bypass path so that anyone can access it without valid token.

But the problem is that filter is always filtering when the request comes into server and, if it's not in the bypass array the request doesn't continue even I declared as @PermitAll.

I would like to know if can I combine those two security options in same web application.

Upvotes: 2

Views: 5551

Answers (1)

cassiomolin
cassiomolin

Reputation: 130867

Since you are performing authentication and/or authorization, instead of servlet filters I would recommend using name binding filters, so you can easily bind them to the resources you need.

To bind filters to your REST endpoints, JAX-RS provides the meta-annotation @NameBinding and can be used as following:

@NameBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface Secured { }

The @Secured annotation will be used to decorate a filter class, which implements ContainerRequestFilter, allowing you to handle the request.

The ContainerRequestContext helps you to extract information from the HTTP request (for more details, have a look at the ContainerRequestContext API):

@Secured
@Provider
@Priority(Priorities.AUTHENTICATION)
public class SecurityFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        // Use the ContainerRequestContext to extract information from the HTTP request
        // Information such as the URI, headers and HTTP entity are available
    }
}

The ContainerRequestFilter#filter() method is a good place to abort the request if the user is not authenticated/authorized. To do it, you can use ContainerRequestContext#abortWith() or throw an exception.

The @Provider annotation marks an implementation of an extension interface that should be discoverable by JAX-RS runtime during a provider scanning phase.

To bind the filter to your endpoints methods or classes, annotate them with the @Secured annotation created above. For the methods and/or classes which are annotated, the filter will be executed.

@Path("/")
public class MyEndpoint {

    @GET
    @Path("{id}")
    @Produces("application/json")
    public Response myUnsecuredMethod(@PathParam("id") Long id) {
        // This method is not annotated with @Secured
        // The security filter won't be executed before invoking this method
        ...
    }

    @DELETE
    @Secured
    @Path("{id}")
    @Produces("application/json")
    public Response mySecuredMethod(@PathParam("id") Long id) {
        // This method is annotated with @Secured
        // The security filter will be executed before invoking this method
        ...
    }
}

In the example above, the security filter will be executed only for mySecuredMethod(Long) because it's annotated with @Secured.

You can have as many filters as you need for your REST endpoints. To ensure the execution order of the filters, annotate them with @Priority.

It's highly recommended to use one of the values defined in the Priorities class (the following order will be used):

If your filter is not annotated with @Priority, the filter will be executed with the USER priority.

You can combine this approach with Jersey security mechanism.

Additionally, you can inject ResourceInfo in your ContainerRequestFilter:

    @Context
    private ResourceInfo resourceInfo;

It can be used to get Method and Class which match with the requested URL:

    Class<?> resourceClass = resourceInfo.getResourceClass();
    Method resourceMethod = resourceInfo.getResourceMethod();

And extract the annotations from them:

    Annotation[] annotations = resourceClass.getDeclaredAnnotations();
    PermitAll annotation = resourceMethod.getAnnotation(PermitAll.class);

Upvotes: 10

Related Questions