【问题标题】:How to mock a SecurityContext如何模拟 SecurityContext
【发布时间】:2014-12-03 18:48:56
【问题描述】:

Jersey 的端点。

我想用ContainerRequestFilter 保护端点

@Provider
@Secured
public class AuthorizationRequestFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        final SecurityContext securityContext =
                requestContext.getSecurityContext();

        //TODO: on logger here...
        System.out.printf("Filtering %s request... AuthorizationRequestFilter\n", requestContext.getMethod());
        requestContext.getHeaders().add("X-Secured-By", "Jersey >_<");
        System.out.printf("SecurityContext: %s (%s).\n", securityContext, securityContext.getAuthenticationScheme());

        if (securityContext == null || !securityContext.isUserInRole("privileged")) {
            requestContext.abortWith(new UnauthorizedResponse().getResponse());
        }
    }
}

注解@Secured

@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Secured {}

所以我可以这样做:

@Path("foobar")
public class FooResource {

    //...

    @Context
    SecurityContext securityContext;

    //...

    @GET
    @Secured
    @Path(value = "foo")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getFoo(@Context SecurityContext sc, @Context UriInfo ui, @Context HttpHeaders hh) {
        // ...
    }

    //...

而且我做得对(我认为),因为在我的测试中,我什至没有通过getFoo 端点,而是将我踢出局的 ContainerRequestFilter。事实上我收到了这个(“X-Secured-By”标题是手工制作的):

Headers: {X-Secured-By=[Jersey >_< kicked you out!], Content-Length=[97], Date=[Wed, 03 Dec 2014 17:46:50 GMT], Content-Type=[application/json], X-Powered-By=[Jersey ^_^]}
Response: InboundJaxrsResponse{ClientResponse{method=GET, uri=http://localhost:9998/urler/test, status=401, reason=Unauthorized}}

现在模拟SecurityContext 会很好。 这就是我正在做的事情......如果我在这里,那显然是愚蠢和/或错误的。

public class UrlerResourceTest extends JerseyTest {
    //....

    @Override
    public TestContainerFactory getTestContainerFactory() {
        GrizzlyTestContainerFactory grizzlyTestContainerFactory = new GrizzlyTestContainerFactory();
        System.out.printf("The GrizzlyTestContainerFactory: %s ", grizzlyTestContainerFactory);
        // just for debugging...
        return grizzlyTestContainerFactory;
    }

    @Test
    public void testSecuredEndpoint() throws JSONException {

        SecurityContext securityContext = Mockito.mock(SecurityContext.class);
        Mockito.when(securityContext.isUserInRole(anyString())).thenReturn(true);
        Mockito.when(securityContext.getAuthenticationScheme()).thenReturn("Just Mocking...");
        ReflectionTestUtils.setField(resource, "securityContext", securityContext, SecurityContext.class);

        final Response response = target("foobar")
            .path("foo")
            .request(MediaType.APPLICATION_JSON)
            .get();
        System.out.println(getFormattedStringResponseInfo(response));

        JSONObject entity = new JSONObject(response.readEntity(String.class));
        assertTrue(entity.get("secured").equals(true));
        assertTrue(response.getHeaders().containsKey("X-Secured-By"));
        assertEquals(Status.OK.getStatusCode(), response.getStatus());
    }

如何在我的测试中模拟 SecurityContext

非常感谢您。

【问题讨论】:

    标签: java unit-testing mocking jax-rs jersey-2.0


    【解决方案1】:

    免责声明:我并不是真正的 Mockito 用户,但据我了解,模拟用于注入类依赖项(字段)并模拟这些依赖项的情况。在这种情况下,您仍然需要使用模拟对象设置字段。例如

    public class TestClass {
        TestService testService;
        public void doTest() {
            System.out.println(testService.getString());
        }
        public void setTestService(TestService testService) {
            this.testService = testService;
        }
    }
    public class TestService {
        public String getString() {
            return "Hello world";
        }
    }
    @Test
    public void toTest() {
        TestService testService = Mockito.mock(TestService.class);
        Mockito.when(testService.getString()).thenReturn("Hello Squirrel");
        TestClass testClass = new TestClass();
        testClass.setTestService(testService);
        testClass.doTest();
    }
    

    您可以看到我们正在使用模拟对象设置TestClass 中的TestService。这不是最好的例子,因为我们可以简单地实例化 TestService,但根据我的理解,它显示了模拟应该如何工作。

    话虽如此,我看不出如何使用AuthorizationRequestFilter 来做到这一点,因为它是由测试容器处理的,而且我们没有为单元测试实例化它。即使我们是这样,添加 SecurityContext 字段似乎也很麻烦(而且是多余的)。

    因此,如果没有完整的集成测试,即我们正在启动服务器并使用服务器的身份验证功能,则每个用例都很难处理 SecurityContext,因为 SecurityContext 是由容器创建的,从底层 servlet 容器身份验证机制中获取信息。

    您可以实现此目的的一种方法(IMO 看起来不是很优雅 - 但有效),没有完整的集成测试,是创建一个过滤器,在您的AuthorizationRequestFilter 之前执行之前,并从那里设置SecurityContext。除了测试之外,这在我们需要实现自己的自定义身份验证机制的情况下实际上很常见。

    您如何为单元测试执行此操作的示例可能类似于:

    public class UrlerResourceTest extends JerseyTest {
        ...
        @Override
        public Application configure() {
            return new ResourceConfig(FooResource.class)
                    .register(AuthorizationRequestFilter.class)
                    .register(AuthenticationFilter.class);
        }
    
        @Provider
        @Priority(Priorities.AUTHENTICATION)
        public static class AuthenticationFilter implements ContainerRequestFilter {
            @Override
            public void filter(ContainerRequestContext requestContext) throws IOException {
                requestContext.setSecurityContext(new SecurityContext() {
                    @Override
                    public Principal getUserPrincipal() {
                        return new Principal() {
                            @Override
                            public String getName() {
                                return "Stackoverflow";
                            }
                        };
                    }
                    @Override
                    public boolean isUserInRole(String string) {
                        return "privileged".equals(string);
                    }
                    @Override
                    public boolean isSecure() { return true; }
                    @Override
                    public String getAuthenticationScheme() { return "BASIC"; }                
                });
            }  
        }
        ...   
    }
    

    由于@Priority 注释,此过滤器将在AuthorizationRequestFilter 之前执行。我们已将其设置为Priorities.AUTHENTICATION,它将在任何其他没有此类注释的过滤器之前。 (参见Priorities APIPriorities with JerseySecurityContext 也将在过滤器之间传递并注入到您的资源类中。

    正如我所说,我认为必须创建另一个过滤器并不是很优雅,但它可以用于此目的。此外,我对 Jersey 测试框架不太熟悉,因为我还是从它开始,但是在 servlet 上下文中部署有许多配置选项。我不知道我们是否可以为这种情况配置所需的身份验证机制,但这可能值得研究。


    编辑:一开始我解释了为测试对象设置字段,但我们也可以将模拟对象传递给方法。例如,我们可以在filter 方法中模拟ContainterRequestContext,并自己调用filter,传递模拟的ContainerRequestContext。但这仅在我们实际对过滤器类进行单元测试并自己实例化它时才有用,这里不是这种情况。

    【讨论】:

    • 谢谢@peeskillet ...您在嘲笑ContainterRequestContext时给了我一个好主意...
    • @peeskillet,想吻你。我在任何地方搜索与泽西岛相关的解决方案,我都会遇到您有用的答案!谢谢!
    • @erwineberhard 我可能很快就要开始收费了 :-)
    猜你喜欢
    • 2019-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-09
    • 2014-11-26
    相关资源
    最近更新 更多