Jersey and HK2 - Injecting current user

2019-02-26 21:24发布

问题:

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

回答1:

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.