Search code examples
javadependency-injectionjerseyjersey-2.0hk2

Jersey and HK2 - Injecting current user


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).


Solution

  • 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.