泽西岛的终点.
我想用一个端点来保护端点 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
在我的测试中嘲笑?
非常感谢你提前.
免责声明:我不是真正的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看起来并不优雅 - 但有效),没有完整的集成测试,就是创建一个在你之前执行的过滤器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.AUTHENTICATION
在没有这种注释的任何其他过滤器之前.(请参阅优先级API和泽西岛的优先级.此外,SecurityContext
它将在过滤器之间传递,也会注入您的资源类.
正如我所说的,我不认为必须创建另一个过滤器是非常优雅的,但它可以用于此目的.另外我对Jersey测试框架不太熟悉,因为我还在开始,但是在servlet上下文中有许多配置选项可供部署.我不知道我们是否可以为这种情况配置所需的身份验证机制,但它可能值得研究.
编辑:在开始我解释了为测试对象设置字段,但我们也可以将模拟对象传递给方法.例如,我们可以ContainterRequestContext
在filter
方法中嘲笑,并打电话给filter
自己,通过模拟ContainerRequestContext
.但是这只有在我们实际对单元测试过滤器类并自己实例化时才有用,这不是这里的情况.