如何模拟SecurityContext

问题描述:

端点Jersey如何模拟SecurityContext

我想用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-担保,通过”报头是手工制作):

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

你可以看到我们正在与嘲笑的对象设置的TestServiceTestClass。这不是最好的例子,因为我们可以简单地实例化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 Jersey。另外,SecurityContext将沿着过滤器之间传递,并注入到你的资源类。

正如我所说的,我不认为这是非常优雅,以创建另一个过滤器,但它我也不太熟悉Jersey测试框架,因为我仍然从头开始,但是在servlet上下文中有很多配置选项可供配置,我不知道我们是否可以配置需要的对于这种情况下的认证机制,但它可能是值得探讨


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

+1

谢谢@peeskillet ...和你在嘲笑'ContainterRequestContext'时给你一个不错的想法... – zeroed 2014-12-04 09:08:38

+0

@peeskillet,想吻你。无论我在哪里搜索与泽西岛有关的解决方案,谢谢! – erwineberhard 2015-12-23 20:13:26

+0

@erwineberhard我可能不得不马上开始收费:-) – 2015-12-24 02:36:42