Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom Method Annotation using Jersey's AbstractHttpContextInjectable not Working

I want to restrict some methods if they are being accessed in a non-secure manner. I'm creating a @Secure annotation that checks whether or not the request was sent over secure channels. However, I cannot create a method injectable that captures the HttpContext of the request.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Secure {

}

public class SecureProvider<T> implements InjectableProvider<Secure, AbstractResourceMethod> {
    @Override
    public ComponentScope getScope() {
        return ComponentScope.PerRequest;
    }

    @Override
    public Injectable<?> getInjectable(ComponentContext componentContext,
                                       Secure annotation,
                                       AbstractResourceMethod method) {
        return new SecureInjectable();
    }
}

public class SecureInjectable<T> extends AbstractHttpContextInjectable<T> {
    @Override
    public T getValue(HttpContext context) {    
        // validation here

        return null;
    }
}

I'm using the Dropwizard framework, so initialization of the Providers should be as easy as:

environment.addProvider(new SessionRestrictedToProvider<>(new SessionAuthenticator(), "MySession"));
environment.addProvider(new SecureProvider<>());
environment.setSessionHandler(new SessionHandler());

Usage:

@Resource
@Path("/account")
public class AccountResource {
    @GET
    @Path("/test_secure")
    @Secure
    public Response isSecure() {
        return Response.ok().build();
    }
}

At this point I'm assuming that a HttpContext Injectable doesn't work on a method, but I'm at a loss as to what other options I could utilize to implement this annotation.

like image 672
IAE Avatar asked Nov 05 '13 08:11

IAE


2 Answers

EDIT this works with JAX-RS 2.0. Though Jersey is now on version 2.4.1, Dropwizard is sadly still using 1.17.1 :(.

You could use a ContainerRequestFilter together with your annotation.

First, the annotation:

// need a name binding annotation
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Secure { }

Next, the filter:

// filter will only be run for methods that have @Secure annotation
@Secure
public class SecureFilter implements ContainerRequestFilter
{
    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException
    {
        // check if HTTPS
        if (!requestContext.getSecurityContext().isSecure())
        {
            // if not, abort the request
            requestContext.abortWith(Response.status(Response.Status.BAD_REQUEST)
                                             .entity("HTTPS is required.")
                                             .build());
        }
    }
}

And lastly, registering the filter. This depends on how you set up your Jersey app. Here are two ways you might have set it up, but there are many other possibilities so I won't cover them all.

If you have a ResourceConfig with grizzly, you would want this:

final ResourceConfig rc = new ResourceConfig()
            .packages("my.package.for.resources")
            .register(SecureFilter.class);

If you're using the custom application model:

public class MyApplication extends ResourceConfig {
    public MyApplication() {
        packages("my.package.for.resources");
        register(SecureFilter.class);
    }
}

Usage:

@Resource
@Path("/account")
public class AccountResource {

    // filter will run for this method
    @GET
    @Path("/test_secure")
    @Secure
    public Response isSecure() {
        return Response.ok().build();
    }

    // filter will NOT run for this method
    @GET
    @Path("/test_insecure")
    public Response allowInsecure() {
        return Response.ok().build();
    }
}
like image 150
Alden Avatar answered Sep 28 '22 07:09

Alden


If you don't want to use AOP, I think you can do this by implementing ResourceMethodDispatchProvider and ResourceMethodDispatchAdapter.

public class CustomDispatchProvider implements ResourceMethodDispatchProvider {

ResourceMethodDispatchProvider provider;

CustomDispatchProvider(ResourceMethodDispatchProvider provider)
{
    this.provider = provider;
}

@Override
public RequestDispatcher create(AbstractResourceMethod abstractResourceMethod) {
    System.out.println("creating new dispatcher for " + abstractResourceMethod);

    RequestDispatcher defaultDispatcher = provider.create(abstractResourceMethod);
    if (abstractResourceMethod.getMethod().isAnnotationPresent(Secure.class))
        return new DispatcherDecorator(defaultDispatcher);
    else
        return defaultDispatcher;
}

@Provider
public static class CustomDispatchAdapter implements ResourceMethodDispatchAdapter
{

    @Override
    public ResourceMethodDispatchProvider adapt(ResourceMethodDispatchProvider provider) {
        return new CustomDispatchProvider(provider);
    }

}

public static class DispatcherDecorator implements RequestDispatcher
{
    private RequestDispatcher dispatcher;

    DispatcherDecorator(RequestDispatcher dispatcher)
    {
        this.dispatcher = dispatcher;
    }

    public void dispatch(Object resource, HttpContext context) {
        if (context.getRequest().isSecure())
        {
            System.out.println("secure request detected");
            this.dispatcher.dispatch(resource, context);
        }
        else
        {
            System.out.println("request is NOT secure");
            throw new RuntimeException("cannot access this resource over an insecure connection");
        }

    }

}
}

In Dropwizard, add the provider like this: environment.addProvider(CustomDispatchAdapter.class);

like image 21
John R Avatar answered Sep 28 '22 09:09

John R