如何模拟SecurityContext

时间:2014-12-03 18:48:57

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

端点为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。事实上我收到了这个(&#34; X-Secured-By&#34;标题是手工制作的):

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

提前非常感谢。

1 个答案:

答案 0 :(得分:9)

免责声明:我不是真正的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();
}

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

话虽如此,我不知道如何使用AuthorizationRequestFilter来执行此操作,因为它是由测试容器处理的,并且我们没有为单元测试实例化它。即使我们这样,添加SecurityContext字段也会产生干扰(并且是多余的)。

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

虽然没有完整的集成测试,但是你可以实现这一目标的一种方式(IMO看起来并不优雅 - 但是有效)是创建一个过滤器,它在你的{{1}之前执行 },并从那里设置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"; } }); } } ... } 注释,此过滤器将在AuthorizationRequestFilter之前执行。我们已将其设置为@Priority,它将在没有此类注释的任何其他过滤器之前。 (请参阅Priorities APIPriorities with JerseyPriorities.AUTHENTICATION也会在过滤器之间传递,也会被注入资源类。

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


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