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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With