Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Injecting principal into resource method in RESTEasy with Guice

I am developing a REST API using RESTEasy with Guice and at the moment I am trying to incorporate basic authentication by use of an annotation similar to the @Auth found in Dropwizard. With

@Path("hello")
public class HelloResource {
    @GET
    @Produces("application/json")
    public String hello(@Auth final Principal principal) {
        return principal.getUsername();
    }
}

the hello resource invocation should be intercepted by some code performing basic authentication using the credentials passed in the Authorization HTTP request header and on success injecting the principal into the method principal parameter. I would also like to be able to pass a list of allowed roles to the annotation, e.g. @Auth("admin").

I really need some advice in what direction to go to achieve this?

like image 729
Stine Avatar asked Jan 09 '14 11:01

Stine


1 Answers

I think your best bet would be using an intermediate value within request scope. Assuming that you didn't put HelloResource in singleton scope, you can inject this intermediate value in some ContainerRequestFilter implementation and in your resource, and you can fill it inside this ContainerRequestFilter implementation with all authentication and authorization info you need.

It will look something like this:

// Authentication filter contains code which performs authentication
// and possibly authorization based on the request
@Provider
public class AuthFilter implements ContainerRequestFilter {
    private final AuthInfo authInfo;

    @Inject
    AuthFilter(AuthInfo authInfo) {
        this.authInfo = authInfo;
    }

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        // You can check request contents here and even abort the request completely
        // Fill authInfo with the data you need
        Principal principal = ...;  // Ask some other service possibly
        authInfo.setPrincipal(principal);
    }
}

@Path("hello")
public class HelloResource {
    private final AuthInfo authInfo;

    @Inject
    HelloResource(AuthInfo authInfo) {
        this.authInfo = authInfo;
    }

    @GET
    @Produces("application/json")
    public String hello() {
        // authInfo here will be pre-filled with the principal, assuming
        // you didn't abort the request in the filter
        return authInfo.getPrincipal().getUsername();
    }
}

public class MainModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(AuthFilter.class);
        bind(HelloResource.class);
        bind(AuthInfo.class).in(RequestScoped.class);
    }
}

And even if you did put the resource (or even the filter) in singleton scope for some reason, you can always inject Provider<AuthInfo> instead of AuthInfo.

Update

It seems that I was somewhat wrong in that the filter is by default not in singleton scope. In fact it seem to behave like singleton even though it is not bound as such. It is created upon JAX-RS container startup. Hence you will need to inject Provider<AuthInfo> into the filter. In fact, the container startup will fail if AuthInfo is injected into the filter directly while being bound to request scope. Resource (if not explicitly bound as singleton) will be OK with direct injection though.

I have uploaded working program to github.

like image 132
Vladimir Matveev Avatar answered Sep 29 '22 09:09

Vladimir Matveev