2014-12-03 12 views
5

Endpoint con Jersey.Come imitare un SecurityContext

Voglio garantire un endpoint con un 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()); 
     } 
    } 
} 

L'annotazione @Secured:

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

modo che io possa fare questo:

@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) { 
     // ... 
    } 

    //... 

E lo sto facendo giusto (credo), perché con il mio test non passo nemmeno attraverso lo getFoo endpoint, ma è ContainerRequestFilter che mi dà il via. In realtà ricevo questo (la "X-Secured-By" intestazione è fatto a mano):

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}} 

Ora sarebbe bello prendere in giro il SecurityContext. Questo è quello che sto facendo ... e se sono qui, è ovviamente sciocco e/o sbagliato.

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

Come posso simulare il SecurityContext nei test?

Grazie mille in anticipo.

risposta

7

Diniego: Io non sono davvero un utente Mockito, ma da quanto ho capito, beffardo è usato per le situazioni in cui è stato iniettato dipendenze di classe (campi), e voi burlate tali dipendenze. In tal caso è ancora necessario impostare il campo con l'oggetto deriso. Per esempio

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

Si può vedere stiamo impostando il il TestService nel TestClass con l'oggetto preso in giro. Non è il più grande esempio, poiché potremmo istanziare semplicemente TestService, ma mostra, da quanto ho capito, come dovrebbe funzionare il mocking.

Detto questo, non vedo come sia possibile farlo con il AuthorizationRequestFilter, poiché viene gestito dal contenitore di test e non lo stiamo istanziando per un test unitario. Anche se lo fossimo, sembrerebbe intrusivo (e ridondante) aggiungere un campo SecurityContext.

Quindi senza un test di integrazione completo, dove stiamo avviando il server e utilizzando le funzionalità di autenticazione del server, sarà difficile gestire lo SecurityContext per questo caso d'uso, poiché lo SecurityContext viene creato dal contenitore, prendendo informazioni da il meccanismo di autenticazione dei contenitori servlet sottostante.

Un modo è possibile raggiungere questo obiettivo però (che IMO non sembra molto elegante - ma funziona), senza un test di integrazione completa, è quello di creare aa filtro che esegue prima tua AuthorizationRequestFilter, e impostare il SecurityContext da ci. A parte i test, questo è piuttosto comune nei casi in cui è necessario implementare il proprio meccanismo di autenticazione personalizzato.

Un esempio di come si potrebbe fare questo per il vostro test di unità, potrebbe essere qualcosa di simile:

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"; }     
      }); 
     } 
    } 
    ... 
} 

Questo filtro si esibirà prima della AuthorizationRequestFilter a causa del @Priority annotazione. Lo abbiamo impostato su Priorities.AUTHENTICATION che verrà prima di qualsiasi altro filtro senza tale annotazione. (Vedere Priorities API e Priorities with Jersey. Inoltre sarà passato il SecurityContext lungo tra i filtri e anche essere iniettato nella vostra classe di risorse.

Come ho detto, non credo che questo è molto elegante, di avere per creare un altro filtro, ma funziona anche per questo scopo, inoltre non ho molta familiarità con il Jersey Test Framework, poiché sto ancora cominciando da esso, ma ci sono molte opzioni di configurazione per la distribuzione all'interno di un contesto di servlet. Non so se possiamo configurare il necessario meccanismo di autenticazione per questo caso, ma potrebbe essere qualcosa che merita di essere esaminata


Edit:. All'inizio ho spiegato di impostare il campo per l'oggetto test, ma possiamo anche passare l'oggetto mocked a un metodo. Ad esempio, potremmo prendere in giro il ContainterRequestContext nel metodo filter e chiamare noi stessi il filter, passando il ContainerRequestContext deriso. Ma questo è utile solo quando siamo in realtà unità di test della classe filtro e istanzialo da soli, il che non è il caso qui.

+1

Grazie @peeskillet ... e con la tua nota su come deridere il 'ContainterRequestContext' mi hai dato una buona idea ... – zeroed

+0

@peeskillet, voglio baciarti. Ovunque cerco soluzioni relative a Jersey, incontro le tue utili risposte! Grazie! – erwineberhard

+0

@erwineberhard Potrei dover iniziare a caricare presto :-) –

Problemi correlati