Custom Method Annotation using Jersey's AbstractHttpContextInjectable not Working

后端 未结 3 823
北荒
北荒 2020-12-08 09:14

I want to restrict some methods if they are being accessed in a non-secure manner. I\'m creating a @Secure annotation that checks whether or not the request was sent over se

相关标签:
3条回答
  • 2020-12-08 09:42

    EDIT this works with JAX-RS 2.0. Though Jersey is now on version 2.4.1, Dropwizard is sadly still using 1.17.1 :(.

    You could use a ContainerRequestFilter together with your annotation.

    First, the annotation:

    // need a name binding annotation
    @NameBinding
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.TYPE})
    public @interface Secure { }
    

    Next, the filter:

    // filter will only be run for methods that have @Secure annotation
    @Secure
    public class SecureFilter implements ContainerRequestFilter
    {
        @Override
        public void filter(ContainerRequestContext requestContext) throws IOException
        {
            // check if HTTPS
            if (!requestContext.getSecurityContext().isSecure())
            {
                // if not, abort the request
                requestContext.abortWith(Response.status(Response.Status.BAD_REQUEST)
                                                 .entity("HTTPS is required.")
                                                 .build());
            }
        }
    }
    

    And lastly, registering the filter. This depends on how you set up your Jersey app. Here are two ways you might have set it up, but there are many other possibilities so I won't cover them all.

    If you have a ResourceConfig with grizzly, you would want this:

    final ResourceConfig rc = new ResourceConfig()
                .packages("my.package.for.resources")
                .register(SecureFilter.class);
    

    If you're using the custom application model:

    public class MyApplication extends ResourceConfig {
        public MyApplication() {
            packages("my.package.for.resources");
            register(SecureFilter.class);
        }
    }
    

    Usage:

    @Resource
    @Path("/account")
    public class AccountResource {
    
        // filter will run for this method
        @GET
        @Path("/test_secure")
        @Secure
        public Response isSecure() {
            return Response.ok().build();
        }
    
        // filter will NOT run for this method
        @GET
        @Path("/test_insecure")
        public Response allowInsecure() {
            return Response.ok().build();
        }
    }
    
    0 讨论(0)
  • 2020-12-08 09:44

    Allowing annotated methods to be accessed only via secure channel can be done using AOP. Please find the solution using Guice and it's AOP capabilities (of course other AOP solutions could be used).

    You will need Guice library (com.google.inject:guice:3.0).

    First of all create annotation

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface Secure {}
    

    then configure guice bundle

    public class SecurableMethodsService extends Service<Configuration> {
    
      @Override
      public void initialize(Bootstrap<Configuration> bootstrap) {
        bootstrap.addBundle(GuiceBundle.newBuilder().addModule(new SecurableMethodsDemonstrationModule()).build());
      }
    
      @Override
      public void run(Configuration configuration, Environment environment) throws Exception {
      }
    
    }
    

    module binds method interceptor

    public class SecurableMethodsDemonstrationModule extends AbstractModule {
    
      @Override
      protected void configure() {
        bind(SecuredMethodsContainingResource.class);
        bindInterceptor(Matchers.any(), Matchers.annotatedWith(Secure.class), new OnlySecureAllowedInterceptor(getProvider(SecurityContext.class)));
      }
    
    }
    

    which checks if connection is secure (note: in this example resource is reported as not found if connection is not secure, you might need to adjust this for your use case)

    public class OnlySecureAllowedInterceptor implements MethodInterceptor {
    
      private final Provider<SecurityContext> securityContextProvider;
    
      public OnlySecureAllowedInterceptor(Provider<SecurityContext> securityContextProvider) {
        this.securityContextProvider = securityContextProvider;
      }
    
      public Object invoke(MethodInvocation invocation) throws Throwable {
        if (!securityContextProvider.get().isSecure()) {
          throw new NotFoundException();
        }
        return invocation.proceed();
      }
    
    }
    

    and finally the resource with secured method looks like

    @Path("")
    public class SecuredMethodsContainingResource {
    
      @GET
      @Path("for-all")
      public String forAll() {
        return "for-all";
      }
    
      @GET
      @Path("secure")
      @Secure
      public String secure() {
        return "secure";
      }
    
    }
    
    0 讨论(0)
  • 2020-12-08 09:50

    If you don't want to use AOP, I think you can do this by implementing ResourceMethodDispatchProvider and ResourceMethodDispatchAdapter.

    public class CustomDispatchProvider implements ResourceMethodDispatchProvider {
    
    ResourceMethodDispatchProvider provider;
    
    CustomDispatchProvider(ResourceMethodDispatchProvider provider)
    {
        this.provider = provider;
    }
    
    @Override
    public RequestDispatcher create(AbstractResourceMethod abstractResourceMethod) {
        System.out.println("creating new dispatcher for " + abstractResourceMethod);
    
        RequestDispatcher defaultDispatcher = provider.create(abstractResourceMethod);
        if (abstractResourceMethod.getMethod().isAnnotationPresent(Secure.class))
            return new DispatcherDecorator(defaultDispatcher);
        else
            return defaultDispatcher;
    }
    
    @Provider
    public static class CustomDispatchAdapter implements ResourceMethodDispatchAdapter
    {
    
        @Override
        public ResourceMethodDispatchProvider adapt(ResourceMethodDispatchProvider provider) {
            return new CustomDispatchProvider(provider);
        }
    
    }
    
    public static class DispatcherDecorator implements RequestDispatcher
    {
        private RequestDispatcher dispatcher;
    
        DispatcherDecorator(RequestDispatcher dispatcher)
        {
            this.dispatcher = dispatcher;
        }
    
        public void dispatch(Object resource, HttpContext context) {
            if (context.getRequest().isSecure())
            {
                System.out.println("secure request detected");
                this.dispatcher.dispatch(resource, context);
            }
            else
            {
                System.out.println("request is NOT secure");
                throw new RuntimeException("cannot access this resource over an insecure connection");
            }
    
        }
    
    }
    }
    

    In Dropwizard, add the provider like this: environment.addProvider(CustomDispatchAdapter.class);

    0 讨论(0)
提交回复
热议问题