I'm working with jersey 2.17 and HK2 to create a simple rest app.
I have a ContainerRequestFilter
that rejects any request that doesn't have the "currentuser" cookie.
I have something like this:
@Path("/users")
public class UserResource {
private UserService userService;
@GET
@Path("/orders")
@Produces("application/json")
public List<Order> findOrdersOfCurrentUser() {
// some ugly code to access headers, extract cookies, and finally
// extract username (a String) from a particular cookie
return this.userService.findOrdersByUsername(username) ;
}
}
I want to code something more elegant than that.
Like this:
@Path("/users")
public class UserResource {
private UserService userService;
@CurrentUsername
private String currentUser;
@GET
@Path("/orders")
@Produces("application/json")
public List<Order> findOrdersOfCurrentUser() {
return this.userService.findOrdersByUsername(username) ;
}
}
I'm really new to hk2 and is getting real hard to find the way to do it.
I'm just asking for the right interface to implement (or class to extend).
What you're looking for is not trivially done. One way you could handle this is setting the SecurityContext
inside the ContainerRequestFilter
, as seen here. This doesn't involve any direct interaction with HK2. You could then inject the SecurityContext
in your resource class. And get the user by
securityContext.getUserPrincipal().getName();
If you really want to go with injecting the username with a custom annotation, you will need to create a InjectionResolver
(See Defining Custom Injection Annotation. You could inject ContainerRequestContext
(the same one passed to the filter method in the ContainerRequestFilter
) or the SecurityContext
into the InjectionResolver
. For example
Filter
@Provider
@PreMatching
public class UserFilter implements ContainerRequestFilter {
public static final String USER_PROP = "user";
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
requestContext.setProperty(USER_PROP, new User("peeskillet"));
}
}
Annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CurrentUser {
}
InjectionResolver
public class CurrentUserInjectionResolver implements InjectionResolver<CurrentUser> {
javax.inject.Provider<ContainerRequestContext> requestContext;
@Inject
public CurrentUserInjectionResolver(
javax.inject.Provider<ContainerRequestContext> requestContext) {
this.requestContext = requestContext;
}
@Override
public Object resolve(Injectee injectee, ServiceHandle<?> sh) {
if (User.class == injectee.getRequiredType()) {
return requestContext.get().getProperty(UserFilter.USER_PROP);
}
return null;
}
@Override
public boolean isConstructorParameterIndicator() { return false; }
@Override
public boolean isMethodParameterIndicator() { return false; }
}
Bind the InjectionResolver
@Provider
public class UserFeature implements Feature {
@Override
public boolean configure(FeatureContext context) {
context.register(new AbstractBinder(){
@Override
public void configure() {
bind(CurrentUserInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<CurrentUser>>(){})
.in(Singleton.class);
}
});
return true;
}
}
Resource
@Path("user")
public class UserResource {
@CurrentUser
private User user;
@GET
public Response getCurrentUser() {
return Response.ok(user.getUsername()).build();
}
}
Now I'm not quite sure about this second approach, at least the part about the filter being a @PreMatching
filter. If I don't make it a pre-matching, the User
will be null. It seems the ContainerRequestContext
does not yet have the property we set, meaning what appears to be happening is the the the InjectResolver
is being called before the filter. I will need to look into this. Making it a pre-matching, IMO should not be required.
Personally though, I would go with the first approach, just using the SecurityContext
. A full example is in the link I provided above. With this approach, you can take advantage of Jersey's RolesAllowedDynamicFeature
if needed.