spring-projects / spring-security

Spring Security
http://spring.io/projects/spring-security
Apache License 2.0
8.72k stars 5.87k forks source link

ConcurrentModificationException in MockMvc.perform #7224

Closed mmyersDE closed 5 years ago

mmyersDE commented 5 years ago

Summary

When using mockMvc.perform on MockMvcRequestBuilders.asyncDispatch with a Spring Security configured applicationContext we receive a ConcurrentModificationException in approximately every 10th test-run. The Exception can be prevented by @DirtiesContext, but this slows down the test alot and can not be used when calling mockMvc.perform multiple times in a single test-method.

Actual Behavior

java.util.ConcurrentModificationException
    at java.base/java.util.LinkedHashMap$LinkedHashIterator.nextNode(LinkedHashMap.java:719)
    at java.base/java.util.LinkedHashMap$LinkedKeyIterator.next(LinkedHashMap.java:741)
    at org.springframework.mock.web.HeaderValueHolder.getByName(HeaderValueHolder.java:98)
    at org.springframework.mock.web.MockHttpServletResponse.doAddHeaderValue(MockHttpServletResponse.java:617)
    at org.springframework.mock.web.MockHttpServletResponse.setHeaderValue(MockHttpServletResponse.java:579)
    at org.springframework.mock.web.MockHttpServletResponse.setHeader(MockHttpServletResponse.java:557)
    at javax.servlet.http.HttpServletResponseWrapper.setHeader(HttpServletResponseWrapper.java:165)
    at org.springframework.security.web.firewall.FirewalledResponse.setHeader(FirewalledResponse.java:50)
    at javax.servlet.http.HttpServletResponseWrapper.setHeader(HttpServletResponseWrapper.java:165)
    at org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.writeHeaders(XFrameOptionsHeaderWriter.java:94)
    at org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterResponse.writeHeaders(HeaderWriterFilter.java:109)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
    at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:182)

Expected Behavior

Test finishes without error.

Version

springBootVersion = '2.1.7.RELEASE'

Sample

@RunWith(SpringRunner.class)
@WebAppConfiguration
@SpringBootTest(classes = Controller.class)
public class ControllerTest {

    @Autowired
    protected WebApplicationContext wac;

    @Test
    public void test() throws Exception {
        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(wac).apply(SecurityMockMvcConfigurers.springSecurity()).build();
        MvcResult result = mockMvc.perform(get("/test"))
                .andExpect(request().asyncStarted()).andReturn();
        mockMvc.perform(asyncDispatch(result)).andExpect(status().isOk());
    }
}
@SpringBootApplication
@RestController
public class Controller extends WebSecurityConfigurerAdapter {

    public static void main(String[] args) {
        SpringApplication.run(Controller.class, args);
    }

    @GetMapping("/test")
    public ResponseEntity<StreamingResponseBody> test() {
        return new ResponseEntity<>(outputStream -> outputStream.write("content".getBytes()), OK);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().permitAll();
    }
}
rwinch commented 5 years ago

@mmerdes Thanks for the report. Could you please turn this into a complete project so it is easier for us to run. I know it is only a little work, but since there are lots of issues logged to the project a little * many issues adds up to a lot of time. Thanks!

mmyersDE commented 5 years ago

https://github.com/mmyersDE/SPSS-7224

Your welcome

rwinch commented 5 years ago

Thanks for the complete sample application.

This appears to be an issue with MockMvc not allowing multiple Threads to operate on the headers at the same time. It happens in Spring Security because the HeaderWriterFilter will attempt to write the headers when it completes and just before the response is committed.

I pushed a branch named nosecurity that reproduces the issue without Spring Security being used to demonstrate the issue is in MockMvc rather than Spring Security. I'd encourage you to submit this issue to Spring Framework for the MockMvc team to look at.

msanaulla commented 4 years ago

Which release of Spring Framework is this fix available? Is it 5.1.11?