8

如果以非安全方式访问它们,我想限制某些方法。我正在创建一个 @Secure 注释,用于检查请求是否通过安全通道发送。但是,我无法创建一个可注入的方法来捕获请求的 HttpContext。

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

}

public class SecureProvider<T> implements InjectableProvider<Secure, AbstractResourceMethod> {
    @Override
    public ComponentScope getScope() {
        return ComponentScope.PerRequest;
    }

    @Override
    public Injectable<?> getInjectable(ComponentContext componentContext,
                                       Secure annotation,
                                       AbstractResourceMethod method) {
        return new SecureInjectable();
    }
}

public class SecureInjectable<T> extends AbstractHttpContextInjectable<T> {
    @Override
    public T getValue(HttpContext context) {    
        // validation here

        return null;
    }
}

我使用的是 Dropwizard 框架,所以提供者的初始化应该很简单:

environment.addProvider(new SessionRestrictedToProvider<>(new SessionAuthenticator(), "MySession"));
environment.addProvider(new SecureProvider<>());
environment.setSessionHandler(new SessionHandler());

用法:

@Resource
@Path("/account")
public class AccountResource {
    @GET
    @Path("/test_secure")
    @Secure
    public Response isSecure() {
        return Response.ok().build();
    }
}

在这一点上,我假设 HttpContext Injectable 不适用于方法,但我不知道我可以利用哪些其他选项来实现此注释。

4

3 回答 3

9

编辑这适用于 JAX-RS 2.0。尽管 Jersey 现在是 2.4.1 版本,但 Dropwizard 遗憾地仍在使用 1.17.1 :(。

您可以将 aContainerRequestFilter与注释一起使用。

一、注解:

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

接下来,过滤器:

// 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());
        }
    }
}

最后,注册过滤器。这取决于您如何设置 Jersey 应用程序。以下是您可能设置的两种方式,但还有许多其他可能性,因此我不会全部介绍。

如果你有一只ResourceConfig灰熊,你会想要这个:

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

如果您使用的是自定义应用程序模型:

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

用法:

@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();
    }
}
于 2013-12-16T13:14:14.553 回答
6

如果你不想使用 AOP,我想你可以通过实现 ResourceMethodDispatchProvider 和 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");
        }

    }

}
}

在 Dropwizard 中,像这样添加提供程序: environment.addProvider(CustomDispatchAdapter.class);

于 2013-12-16T19:13:56.530 回答
3

允许仅通过安全通道访问带注释的方法可以使用 AOP 完成。请使用 Guice 找到解决方案及其 AOP 功能(当然也可以使用其他 AOP 解决方案)。

您将需要 Guice 库 (com.google.inject:guice:3.0)。

首先创建注解

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

然后配置 guice 包

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 {
  }

}

模块绑定方法拦截器

public class SecurableMethodsDemonstrationModule extends AbstractModule {

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

}

检查连接是否安全(注意:在此示例中,如果连接不安全,则报告资源未找到,您可能需要针对您的用例进行调整)

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();
  }

}

最后具有安全方法的资源看起来像

@Path("")
public class SecuredMethodsContainingResource {

  @GET
  @Path("for-all")
  public String forAll() {
    return "for-all";
  }

  @GET
  @Path("secure")
  @Secure
  public String secure() {
    return "secure";
  }

}
于 2013-12-16T15:10:06.580 回答