如何在球衣中保护程序化资源?

How to securing programmatic resources in a jersey?

球衣 JAX-RS 资源可以使用这样的注释来保护。

@RolesAllowed("user")
@GET
public String get() { return "GET"; }

我的要求是保护我这样创建的动态创建的球衣资源

@ApplicationPath("/")
public class MyApp extends ResourceConfig {
    public MyApp() {

        packages("com.test.res");
        Resource.Builder resourceBuilder = Resource.builder();
        resourceBuilder.path("/myresource3");

        final ResourceMethod.Builder methodBuilder = resourceBuilder.addMethod("GET");      
        methodBuilder.produces(MediaType.TEXT_PLAIN).handledBy(new TestInflector());

        Resource resource = resourceBuilder.build();
        registerResources(resource);        
        register(RolesAllowedDynamicFeature.class);
    }

}

如何只允许 "user" 访问这个动态创建的资源?

不幸的是,RolesAllowedDynamicFeature 似乎不支持使用程序化 API 创建的资源。如果您查看 source for RolesAllowedDynamicFeature, you'll see that it looks for the annotations on the resource method and/or resource class to determine whether or not the resource/method should be attached to the filter that handles the authorization.

我能想到的最好的方法就是在Inflector中进行授权。你可以看到我链接到的源代码,处理授权的过滤器并没有做太多事情。它只是检查 SecurityContext 是否允许角色。您可以使用 Inflector 中的逻辑。例如

public static class AuthorizationInflector
        implements Inflector<ContainerRequestContext, Response> {

    private final String[] rolesAllowed;
    private final Inflector<ContainerRequestContext, Response> delegate;

    protected AuthorizationInflector(String[] rolesAllowed,
                                     Inflector<ContainerRequestContext, Response> delegate) {
        this.rolesAllowed = (rolesAllowed != null) ? rolesAllowed : new String[] {};
        this.delegate = delegate;
    }

    @Override
    public Response apply(ContainerRequestContext context) {
        applyAuthorization(context);

        return this.delegate.apply(context);
    }


    private void applyAuthorization(ContainerRequestContext requestContext) {
        if (rolesAllowed.length > 0 && !isAuthenticated(requestContext)) {
            throw new ForbiddenException(LocalizationMessages.USER_NOT_AUTHORIZED());
        }

        for (final String role : rolesAllowed) {
            if (requestContext.getSecurityContext().isUserInRole(role)) {
               return;
           }
        }
        throw new ForbiddenException(LocalizationMessages.USER_NOT_AUTHORIZED());
    }

    private static boolean isAuthenticated(final ContainerRequestContext requestContext) {
        return requestContext.getSecurityContext().getUserPrincipal() != null;
    }
}

它看起来与 RolesAllowedRequstFilter 中的代码非常相似。我们正在处理授权,然后将 return 委托给另一个 Inflector。你会像

一样使用它
final String[] rolesAllowed = {"USER"};
methodBuilder.produces(MediaType.TEXT_PLAIN_TYPE)
            .handledBy(new AuthorizationInflector(rolesAllowed, new TestInflector()));

唯一真正明显的行为差异(使用变形器而不是过滤器)是过滤器具有顺序优先级的概念。您可以在 RolesAllowedRequestFilter 中看到它使用 Priorities.AUTHORIZATION 的优先级。它使用它的原因是因为在此过滤器之前发生的身份验证过滤器应该使用优先级 Priorities.AUTHENTICATION,这确保身份验证在授权之前发生。

在使用变形器的情况下,您仍然有此顺序,即身份验证过滤器发生在变形器应用之前。行为的不同之处在于你想实现一些其他过滤器,你希望它在授权后执行,所以你可能有这个

@Priority(Priorities.AUTORIZATION + 100)
class SomeFilter implements ContainerRequestFilter {}

也许您需要对用户进行授权。使用变形器时的问题是它不会在 这个过滤器之后被调用。这不是您想要的,因为它取决于完成的授权。

这是使用变形器进行授权的一个缺点。

我能想到的另一件事 可能 起作用(虽然我还没有把所有的部分放在一起,就是使用名称绑定。

@NameBinding
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Authorization {}

methodBuilder
    .nameBinding(Authorization.class)
    .produces(MediaType.TEXT_PLAIN)
    .handledBy(new TestInflector());

@Authorization
public class AuthorizationFilter implements ContainerRequestFilter {}

您可以像 RolesAllowedRequestFilter 一样实现 AuthorizationFilter。我还没有弄清楚的是如何从过滤器内部获得允许的角色。您显然不能只将它传递给过滤器,因为它需要根据资源方法确定范围。我不确定这是否可以完成。这是我需要进一步研究的东西。

目前,我唯一能想到的我已经测试过并且有效的方法就是使用变形器。