使用@WithMockUser的“放心测试”测试适用于GET请求,但不适用于POST(403错误)

时间:2019-07-12 20:04:58

标签: spring-boot post http-status-code-403 rest-assured

在我的Spring Boot 2应用程序中,我正在使用Rest Assured测试我的一个控制器。此控制器在类级别上映射为/routing-status,并具有3个端点:全部获取,通过ID获取和POST。

我为GET端点进行的“确保放心”测试工作正常,但是由于某种原因,POST测试始终导致403错误。 (java.lang.AssertionError: 1 expectation failed. Expected status code <201> but was <403>.)我想不通为什么,考虑到整个控制器的安全性是相同的,并且我的测试为每个端点使用相同的模拟用户配置。

也许是安全性设置中的其他问题仅影响POST(或者至少不影响GET)?下面是我当前的安全配置,尽管我一直在弄乱端点通配符(无济于事),以查看是否没有尾随斜杠会起作用。

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

以下是我的POST Rest Asured测试失败。 GET请求全部使用具有相同常量值的完全相同的@WithMockUser批注,并且它们都命中相同的根端点/routing-status(尽管对于GET by ID,附加的{{1} })。我尝试在末尾加斜杠,并为POST URL保留斜杠,但是没有运气。

/{ID}

为达到良好效果,以下是控制器的相关部分:

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

为弹簧安全性而打开调试日志记录时(使用@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); } } 属性),错误发生之前的输出为(时间戳,日志级别[DEBUG],完全限定的类名[所有spring boot / security软件包) ],为简洁起见,删除了其他一些不相关的细节):

logging.level.org.springframework.security=DEBUG

因此,似乎csrf可能是一个问题?但是,当应用程序运行时,csrf可以正常工作,我不确定为什么要检查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 。在本地运行时,http://localhost/routing-statuslocalhost之间有一个端口号和一个上下文路径。

2 个答案:

答案 0 :(得分:0)

我得到403的原因是由于测试中没有CSRF令牌。我对CSRF并不是很熟悉,但是根据到目前为止的发现,它并没有影响GET请求,因为CSRF仅影响POST和PUT等写请求。我仍在努力将CSRF步骤添加到RestAssured测试中,但是至少现在我知道了造成这种情况的原因。我打算按照Rest Assured的github页面上的说明完全解决问题:https://github.com/rest-assured/rest-assured/wiki/usage#csrf

答案 1 :(得分:0)

此问题的完美解决方案如下:

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