En mi aplicación BOOT 2 de primavera, estoy usando el descanso asegurado para probar uno de mis controladores. Este controlador se asigna a nivel de clase para ser /routing-status y tiene 3 puntos finales: A conseguir todo, una identificación y una publicación.

Los exámenes asegurados en el resto que tengo para los puntos finales de Obtener funcionan bien, pero por cualquier motivo, la prueba posterior sigue resultando en un error 403. (java.lang.AssertionError: 1 expectation failed. Expected status code <201> but was <403>.) No puedo averiguar por qué, considerando que la seguridad es la misma para todo el controlador y mi prueba usa la misma configuración de usuario simulada para cada punto final.

Tal vez sea algún otro problema con la configuración de seguridad que solo afecta las publicaciones (o al menos lo menos no afecta)? A continuación se muestra mi configuración de seguridad actual, aunque había estado jugando con los comodines de Endpoint (sin éxito) para ver si no tiene la barra de arrastre funcionaría.

httpSecurity
            .csrf().csrfTokenRepository(csrfTokenRepository)
            .and().cors()
            .and().addFilter(createCasAuthFilter())
            .headers().frameOptions().disable()
            .and().exceptionHandling().authenticationEntryPoint(noRedirectAuthenticationEntryPoint)
            .and().authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS).permitAll()
            .antMatchers("/login/redirect").authenticated()
            .antMatchers("/login/**", "/status/**", "/error/**", "/smoke-test/**", LOGOUT_PATH).permitAll()
        // BELOW IS THE CONTROLLER ENDPOINT IN QUESTION    
            .antMatchers("/routing-status/**").hasRole("ROUTING_STATUS_ADMIN")
        // ABOVE IS THE CONTROLLER ENDPOINT IN QUESTION
            .antMatchers(HttpMethod.GET, "/user/**").authenticated()
            .antMatchers("/hello-user**", "/exit-user**", "/roles-map**").authenticated()
            .anyRequest().denyAll().and()
            .logout()
            .logoutRequestMatcher(new AntPathRequestMatcher(LOGOUT_PATH))
            .logoutSuccessUrl(casUrl + LOGOUT_PATH)
            .and().authorizeRequests();

A continuación se muestra mi prueba de poste fallada asegurada. Las solicitudes de obtención de obtención usan la misma anotación de la misma @WithMockUser con los mismos valores constantes, y todos llegan al mismo punto final raíz /routing-status (aunque en el caso de la ID de entradaidad, un adicional {{x2} } esta pasado). Lo intenté, incluida la barra de arrastre y dejando la slash final para la URL POST, pero no hay suerte.

private static final String ADMIN_ROLE = "ROUTING_STATUS_ADMIN";


/* Other fields and methods omitted */

@Test
@WithMockUser(username = TEST_USERNAME, roles = {ADMIN_ROLE})
public void testInsertRoutingStatus() {
    RoutingStatus status = createRoutingStatus();

    RoutingStatus result = given().mockMvc(mockMvc).contentType(ContentType.JSON).log().all()
            .and().body(status)
            .when().post("/routing-status/")
            .then().log().all().statusCode(201)
            .and().body(matchesJsonSchemaInClasspath("json-schemas/routing-status.json"))
            .extract().as(RoutingStatus.class);
}

Para una buena medida, aquí están las partes relevantes del controlador:

@Controller
@RequestMapping(value = "/routing-status", produces = MediaType.APPLICATION_JSON_VALUE)
public class RoutingStatusController {
    /* Other properties and endpoints omitted */

    @PostMapping(value = "")
    public ResponseEntity<RoutingStatus> insertRoutingStatus(
        @Valid @RequestBody final RoutingStatus routingStatus, final Principal principal) {

        return new ResponseEntity<>(routingStatusService.saveNewRoutingStatus(routingStatus, principal.getName()),
            HttpStatus.CREATED);
    }
}

Cuando la tala de depuración se enciende para la seguridad de la primavera (usando la propiedad logging.level.org.springframework.security=DEBUG), la salida derecha antes de que el error sea (marcas de tiempo, nivel de registro [depuración], nombres de clase totalmente calificados [todos los paquetes de arranque / seguridad de resortes], y Algunos otros detalles irrelevantes fueron eliminados por brevedad):

HttpSessionSecurityContextRepository:174 - No HttpSession currently exists
HttpSessionSecurityContextRepository:116 - No SecurityContext was available from the HttpSession: null. A new one will be created.
HttpSessionSecurityContextRepository$SaveToSessionResponseWrapper:423 - HttpSession being created as SecurityContext is non-default
HttpSessionSecurityContextRepository$SaveToSessionResponseWrapper:377 - SecurityContext 'SecurityContextImpl@6ea1035d: Authentication: UsernamePasswordAuthenticationToken@6ea1035d: Principal: User@e85371e3: Username: TEST_USERNAME; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ROUTING_STATUS_ADMIN; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_ROUTING_STATUS_ADMIN' stored to HttpSession: 'MockHttpSession@294ab038
FilterChainProxy$VirtualFilterChain:328 - /routing-status/ at position 1 of 13 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
FilterChainProxy$VirtualFilterChain:328 - /routing-status/ at position 2 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
HttpSessionSecurityContextRepository:207 - Obtained a valid SecurityContext from SPRING_SECURITY_CONTEXT: 'SecurityContextImpl@6ea1035d: Authentication: UsernamePasswordAuthenticationToken@6ea1035d: Principal: User@e85371e3: Username: TEST_USERNAME; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ROUTING_STATUS_ADMIN; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_ROUTING_STATUS_ADMIN'
FilterChainProxy$VirtualFilterChain:328 - /routing-status/ at position 3 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter'
FilterChainProxy$VirtualFilterChain:328 - /routing-status/ at position 4 of 13 in additional filter chain; firing Filter: 'CorsFilter'
FilterChainProxy$VirtualFilterChain:328 - /routing-status/ at position 5 of 13 in additional filter chain; firing Filter: 'CsrfFilter'
CsrfFilter:110 - Invalid CSRF token found for http://localhost/routing-status/
HstsHeaderWriter:129 - Not injecting HSTS header since it did not match the requestMatcher HstsHeaderWriter$SecureRequestMatcher@247415be
SecurityContextPersistenceFilter:119 - SecurityContextHolder now cleared, as request processing completed
403 Forbidden

¿Así que parece que quizás CSRF podría ser un problema? Sin embargo, CSRF funciona bien cuando la aplicación está funcionando, y no estoy seguro de por qué está revisando http://localhost/routing-status. Cuando se ejecuta localmente, hay un número de puerto y una ruta de contexto dentro de localhost y /routing-status.

1
AEvans 12 jul. 2019 a las 23:04

2 respuestas

La mejor respuesta

La razón por la que obtuve un 403 se debe al hecho de que no había un token de CSRF en la prueba. No estoy súper familiarizado con el CSRF, sino basado en lo que he encontrado hasta ahora, no afectó las solicitudes de obtención porque el CSRF solo afecta a las solicitudes de escritura como Publicar y poner. Todavía estoy trabajando en agregar pasos CSRF a las pruebas restauradas, pero al menos ahora conozco la causa del comportamiento. Tengo la intención de seguir las instrucciones en la página de GitHub de SEST Assured para resolver completamente el problema: https://github.com/rest-assured/rest-assured/wiki/usage#csrf

0
AForsberg 15 jul. 2019 a las 20:11

La solución perfecta para este problema está a continuación:

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class TestClass {
    @Autowired
    private WebApplicationContext webApplicationContext;

   @BeforeEach
   public void setUp() {
     RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
     RestAssuredMockMvc.postProcessors(csrf().asHeader());
   }
}
0
Siddu 10 sep. 2020 a las 22:47