title: Custom filter breaking @WebMvcTest out of nowhere
body: <p>I'm implementing JWT auth in my application. Everything works fine, but when I run my already existing @WebMvcTests and check for the returned status code, they all fail with an "Actual   :403".</p>
  
  <p>This is one of my current test suites:</p>
  
  <pre><code>@WebMvcTest(controllers = UserController.class)
  @ContextConfiguration(classes = {JwtServiceImpl.class}) // custom filter dependency
  class UserControllerTest {
  
      /**
       * Mocked bean because it's a dependency of the SecurityConfiguration
       */
      @MockBean
      private UserDetailsService userDetailsService;
  
      @MockBean
      private UserService userService;
  
      @Autowired
      private ObjectMapper jsonMapper;
  
      @Autowired
      private MockMvc mockMvc;
  
      @Test
      void create_should_return_registered_user_when_request_is_valid() throws Exception {
          // given
          final String EMAIL = "test@test.com";
          final String PASSWORD = "test_password";
          final UserDto userDto = buildDto(EMAIL, PASSWORD);
          final User expectedUser = buildUser(EMAIL, PASSWORD);
  
          // when
          when(userService.registerUser(userDto)).thenReturn(expectedUser);
  
          // then
          MvcResult response = mockMvc.perform(post(UserAPI.BASE_URL)
                  .contentType(MediaType.APPLICATION_JSON)
                  .content(jsonMapper.writeValueAsString(userDto)))
                  .andExpect(status().isCreated())
                  .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                  .andReturn();
  
          String responseBodyJson = response.getResponse().getContentAsString();
          User responseUser = jsonMapper.readValue(responseBodyJson, User.class);
  
          assertThat(responseUser.getId(), is(equalTo(expectedUser.getId())));
          assertThat(responseUser.getEmail(), is(equalTo(expectedUser.getEmail())));
          assertThat(responseUser.getPassword(), is(nullValue()));
  
          verify(userService, times(1)).registerUser(userDto);
          verifyNoMoreInteractions(userService);
      }
  
      ...
  
  }
  </code></pre>
  
  <p>And this is my custom filter:</p>
  
  <pre><code>@Slf4j
  @Component
  @RequiredArgsConstructor
  public class AuthorizationFilter extends OncePerRequestFilter {
  
      public static final String AUTHORIZATION_HEADER_KEY = "Authorization";
      public static final String AUTHORIZATION_HEADER_PREFIX = "Bearer ";
  
      private final JwtService jwtService;
      private final UserDetailsService userDetailsService;
  
      @Override
      protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
              throws ServletException, IOException {
  
          String header = request.getHeader(AUTHORIZATION_HEADER_KEY);
  
          if (hasText(header) &amp;&amp; header.startsWith(AUTHORIZATION_HEADER_PREFIX)) {
              String jwt = header.substring(AUTHORIZATION_HEADER_PREFIX.length());
              Authentication establishedPrincipal = SecurityContextHolder.getContext().getAuthentication();
  
              if (!jwtService.isTokenExpired(jwt) &amp;&amp; establishedPrincipal == null) {
                  try {
  
                      String username = jwtService.extractUsername(jwt);
  
                      try {
  
                          UserDetails principal = userDetailsService.loadUserByUsername(username);
  
                          setRequestSession(request, principal);
  
                      } catch (UsernameNotFoundException exception) {
                          log.warn("Could not find user: {} extracted from jwt: {}", username, jwt);
                      }
  
                  } catch (ExpiredJwtException exception) {
                      log.warn("Request to parse expired JWT: {} failed: {}", jwt, exception.getMessage());
                  } catch (UnsupportedJwtException exception) {
                      log.warn("Request to parse unsupported JWT: {} failed: {}", jwt, exception.getMessage());
                  } catch (MalformedJwtException exception) {
                      log.warn("Request to parse invalid JWT: {} failed: {}", jwt, exception.getMessage());
                  } catch (SignatureException exception) {
                      log.warn("Request to parse JWT with invalid signature: {} failed: {}", jwt, exception.getMessage());
                  } catch (IllegalArgumentException exception) {
                      log.warn("Request to parse empty or null JWT: {} failed: {}", jwt, exception.getMessage());
                  }
              }
          }
  
          filterChain.doFilter(request, response);
      }
  
      private void setRequestSession(HttpServletRequest request, UserDetails principal) {
          UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(principal,
                  null, principal.getAuthorities());
  
          authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
  
          SecurityContextHolder.getContext().setAuthentication(authToken);
      }
  
  }
  </code></pre>
  
  <p>Apparently MockMvc will pick the filter automatically. If I remove @Component from the filter, it will not be picked up by the Spring Context anymore and the tests all pass!</p>
  
  <p>So the problem is clearly the filter but I've already set a breakpoint in the first line of doFilter and it was not hit...</p>
  
  <p>Maybe I should configure MockMvc myself instead of autowiring it? But how?</p>
  
  <p>Here's the full output of the test run:</p>
  
  <pre><code>MockHttpServletRequest:
        HTTP Method = POST
        Request URI = /api/v1/users
         Parameters = {}
            Headers = [Content-Type:"application/json", Content-Length:"52"]
               Body = &lt;no character encoding set&gt;
      Session Attrs = {org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN=org.springframework.security.web.csrf.DefaultCsrfToken@62735b13}
  
  Handler:
               Type = null
  
  Async:
      Async started = false
       Async result = null
  
  Resolved Exception:
               Type = null
  
  ModelAndView:
          View name = null
               View = null
              Model = null
  
  FlashMap:
         Attributes = null
  
  MockHttpServletResponse:
             Status = 403
      Error message = Forbidden
            Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
       Content type = null
               Body = 
      Forwarded URL = null
     Redirected URL = null
            Cookies = []
  
  
  java.lang.AssertionError: Status expected:&lt;400&gt; but was:&lt;403&gt;
  Expected :400
  Actual   :403
  &lt;Click to see difference&gt;
  
  
      at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
      at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122)
      at org.springframework.test.web.servlet.result.StatusResultMatchers.lambda$matcher$9(StatusResultMatchers.java:627)
      at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:196)
      at com.example.ordersapi.user.controller.UserControllerTest.create_should_return_bad_request_when_request_has_invalid_email(UserControllerTest.java:111)
      at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
      at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      at java.base/java.lang.reflect.Method.invoke(Method.java:566)
      at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:675)
      at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
      at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:125)
      at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:132)
      at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:124)
      at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:74)
      at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
      at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
      at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:104)
      at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:62)
      at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:43)
      at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:35)
      at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
      at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
      at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:202)
      at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
      at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:198)
      at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
      at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
      at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
      at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
      at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
      at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
      at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
      at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
      at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
      at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
      at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
      at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
      at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
      at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
      at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
      at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
      at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
      at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
      at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
      at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)
      at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)
      at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)
      at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
      at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
      at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
      at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
      at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
      at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
  
      ...
  </code></pre>
  
  <p>If I set <code>logging.level.org.springframework.security=DEBUG</code> I can also see something interesting in the logs:</p>
  
  <pre><code>:: Spring Boot ::  (v2.2.2.RELEASE)
  2020-01-16 18:57:04.484  INFO 30809 --- [           main] c.e.o.u.controller.UserControllerTest    : Starting UserControllerTest on Joaos-MBP.lan with PID 30809 (started by joao in /Users/joao/Projects/orders-api-spring-web-mvc)
  2020-01-16 18:57:04.486 DEBUG 30809 --- [           main] c.e.o.u.controller.UserControllerTest    : Running with Spring Boot v2.2.2.RELEASE, Spring v5.2.2.RELEASE
  2020-01-16 18:57:04.499  INFO 30809 --- [           main] c.e.o.u.controller.UserControllerTest    : No active profile set, falling back to default profiles: default
  2020-01-16 18:57:06.389  INFO 30809 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
  2020-01-16 18:57:06.736 DEBUG 30809 --- [           main] eGlobalAuthenticationAutowiredConfigurer : Eagerly initializing {org.springframework.boot.autoconfigure.security.servlet.WebSecurityEnablerConfiguration=org.springframework.boot.autoconfigure.security.servlet.WebSecurityEnablerConfiguration@1d289d3f}
  2020-01-16 18:57:06.806 DEBUG 30809 --- [           main] s.s.c.a.w.c.WebSecurityConfigurerAdapter : Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).
  2020-01-16 18:57:06.898 DEBUG 30809 --- [           main] edFilterInvocationSecurityMetadataSource : Adding web access control expression 'authenticated', for any request
  2020-01-16 18:57:06.909 DEBUG 30809 --- [           main] o.s.s.w.a.i.FilterSecurityInterceptor    : Validated configuration attributes
  2020-01-16 18:57:06.912 DEBUG 30809 --- [           main] o.s.s.w.a.i.FilterSecurityInterceptor    : Validated configuration attributes
  2020-01-16 18:57:06.932  INFO 30809 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@7911cc15, org.springframework.security.web.context.SecurityContextPersistenceFilter@5e3db14, org.springframework.security.web.header.HeaderWriterFilter@2aea717c, org.springframework.security.web.csrf.CsrfFilter@57cabdc3, org.springframework.security.web.authentication.logout.LogoutFilter@78d92eef, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@27ab206, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@7b9e25bd, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@4409cae6, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@34f7b44f, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@1ee40b5c, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@4a22e4d7, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@14e750c5, org.springframework.security.web.session.SessionManagementFilter@7d133fb7, org.springframework.security.web.access.ExceptionTranslationFilter@37d3e140, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@3de79067]
  2020-01-16 18:57:07.011  INFO 30809 --- [           main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
  2020-01-16 18:57:07.011  INFO 30809 --- [           main] o.s.t.web.servlet.TestDispatcherServlet  : Initializing Servlet ''
  2020-01-16 18:57:07.022  INFO 30809 --- [           main] o.s.t.web.servlet.TestDispatcherServlet  : Completed initialization in 10 ms
  2020-01-16 18:57:07.071  INFO 30809 --- [           main] c.e.o.u.controller.UserControllerTest    : Started UserControllerTest in 3.236 seconds (JVM running for 5.213)
  
  2020-01-16 18:57:07.192 DEBUG 30809 --- [           main] o.s.security.web.FilterChainProxy        : /api/v1/users at position 1 of 15 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
  2020-01-16 18:57:07.193 DEBUG 30809 --- [           main] o.s.security.web.FilterChainProxy        : /api/v1/users at position 2 of 15 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
  2020-01-16 18:57:07.194 DEBUG 30809 --- [           main] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
  2020-01-16 18:57:07.194 DEBUG 30809 --- [           main] w.c.HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: null. A new one will be created.
  2020-01-16 18:57:07.196 DEBUG 30809 --- [           main] o.s.security.web.FilterChainProxy        : /api/v1/users at position 3 of 15 in additional filter chain; firing Filter: 'HeaderWriterFilter'
  2020-01-16 18:57:07.197 DEBUG 30809 --- [           main] o.s.security.web.FilterChainProxy        : /api/v1/users at position 4 of 15 in additional filter chain; firing Filter: 'CsrfFilter'
  2020-01-16 18:57:07.199 DEBUG 30809 --- [           main] o.s.security.web.csrf.CsrfFilter         : Invalid CSRF token found for http://localhost/api/v1/users
  2020-01-16 18:57:07.199 DEBUG 30809 --- [           main] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@1de5cc88
  2020-01-16 18:57:07.199 DEBUG 30809 --- [           main] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
  2020-01-16 18:57:07.201 DEBUG 30809 --- [           main] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
  </code></pre>
  
  <p>Special attention the the line <code>2020-01-16 18:57:07.199 DEBUG 30809 --- [           main] o.s.security.web.csrf.CsrfFilter         : Invalid CSRF token found for http://localhost/api/v1/users</code> ! I have csrf disabled in my configuration and it is also being picked up because I'm wiring a dependency in the test specially for it (otherwise the context initialization fails).</p>
  
  <p>The security configuration is the following:</p>
  
  <pre><code>@EnableWebSecurity
  @RequiredArgsConstructor
  public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
  
      @Value("${spring.h2.console.enabled:false}")
      private boolean h2ConsoleEnabled;
  
      private final UserDetailsService userDetailsService;
      // private final AuthorizationFilter authorizationFilter;
  
      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
          auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
      }
  
      @Override
      protected void configure(HttpSecurity http) throws Exception {
          if (h2ConsoleEnabled) {
              http.authorizeRequests()
                  .antMatchers("/h2-console", "/h2-console/**").permitAll()
                  .and()
                  .headers().frameOptions().sameOrigin();
          }
  
          http.cors().and().csrf().disable()
              .exceptionHandling()
                  .authenticationEntryPoint(unauthorizedHandler())
                  .and()
              .sessionManagement()
                  .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                  .and()
              .authorizeRequests()
                  .antMatchers(AuthenticationAPI.BASE_URL + "/**").permitAll()
                  .antMatchers(ProductAPI.BASE_URL + "/**").permitAll()
                  .antMatchers(UserAPI.BASE_URL + "/**").permitAll()
              .anyRequest().authenticated();
  
          // http.addFilterBefore(authorizationFilter, UsernamePasswordAuthenticationFilter.class);
      }
  
      private AuthenticationEntryPoint unauthorizedHandler() {
          return (request, response, e) -&gt; response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
      }
  
      /**
       * We need to override this method in order to add the @Bean annotation because Spring doesn't create an AuthenticationManager bean by default anymore.
       * Without this we can't wire AuthenticationManager in other beans.
       * @return AuthenticationManager bean
       * @throws Exception on unsuccessful bean creation
       */
      @Bean
      @Override
      public AuthenticationManager authenticationManagerBean() throws Exception {
          return super.authenticationManagerBean();
      }
  
      @Bean
      public PasswordEncoder passwordEncoder() {
          return NoOpPasswordEncoder.getInstance();
      }
  }
  </code></pre>
  
  <p>What I was expecting to happen: that the tests kept green because they do not even target protected routes. It should be transparent to that controller.</p>
  
  <p>What actually happens: Custom filter being automatically picked up by Spring Context is breaking the tests. If I unregister it as a spring bean (remove @Component) everything goes back to normal.</p>