Custom filter breaking @WebMvcTest out of nowhere

断了今生、忘了曾经 提交于 2020-03-25 13:42:07

问题


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".

This is one of my current test suites:

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

    ...

}

And this is my custom filter:

@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) && header.startsWith(AUTHORIZATION_HEADER_PREFIX)) {
            String jwt = header.substring(AUTHORIZATION_HEADER_PREFIX.length());
            Authentication establishedPrincipal = SecurityContextHolder.getContext().getAuthentication();

            if (!jwtService.isTokenExpired(jwt) && 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);
    }

}

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!

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

Maybe I should configure MockMvc myself instead of autowiring it? But how?

Here's the full output of the test run:

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /api/v1/users
       Parameters = {}
          Headers = [Content-Type:"application/json", Content-Length:"52"]
             Body = <no character encoding set>
    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:<400> but was:<403>
Expected :400
Actual   :403
<Click to see difference>


    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)

    ...

If I set logging.level.org.springframework.security=DEBUG I can also see something interesting in the logs:

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

Special attention the the line 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 ! 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).

The security configuration is the following:

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

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.

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.


回答1:


FYI, Spring is using its default security configuration and that is why things get nasty.

I'm not sure exactly what's the impact of enabling and disabling my custom filter, and why it was making the test fail just by being there. Either way, to fix the problem I had to use @Import to import my own security config. Then I had to mock its dependencies.

I created a helper class so I don't pollute the actual controller tests:

@Import(SecurityConfiguration.class)
public abstract class SecurityEnabledSetup {

    /**
     * Mocked bean because it's a dependency of the SecurityConfiguration
     */
    @MockBean
    protected UserDetailsService userDetailsService;

    /**
     * Mocked bean because it's a dependency of the SecurityConfiguration
     */
    @MockBean
    protected JwtService jwtService;

}

And my final test class is I expected it to be:

@WebMvcTest(controllers = UserController.class)
class UserControllerTest extends SecurityEnabledSetup {

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

    @Test
    void create_should_return_conflict_when_request_valid_but_email_in_use() throws Exception {
        // given
        final String EMAIL = "test@test.com";
        final String PASSWORD = "test_password";
        final UserDto userDto = buildDto(EMAIL, PASSWORD);

        // when
        when(userService.registerUser(userDto)).thenThrow(new EmailAlreadyInUseException(EMAIL));

        // then
        mockMvc.perform(post(UserAPI.BASE_URL)
                .contentType(MediaType.APPLICATION_JSON)
                .content(jsonMapper.writeValueAsString(userDto)))
                .andExpect(status().isConflict());

        verify(userService, times(1)).registerUser(userDto);
        verifyNoMoreInteractions(userService);
    }

    @Test
    void create_should_return_bad_request_when_request_has_invalid_email() throws Exception {
        // given
        final String BAD_EMAIL = "test_test.com";
        final String PASSWORD = "test_password";
        final UserDto userDto = buildDto(BAD_EMAIL, PASSWORD);

        // when

        // then
        mockMvc.perform(post(UserAPI.BASE_URL)
                .contentType(MediaType.APPLICATION_JSON)
                .content(jsonMapper.writeValueAsString(userDto)))
                .andExpect(status().isBadRequest());

        verifyNoInteractions(userService);
    }

    @Test
    void create_should_return_bad_request_when_request_has_invalid_password() throws Exception {
        // given
        final String EMAIL = "test@test.com";
        final String BAD_PASSWORD = "";
        final UserDto userDto = buildDto(EMAIL, BAD_PASSWORD);

        // when

        // then
        mockMvc.perform(post(UserAPI.BASE_URL)
                .contentType(MediaType.APPLICATION_JSON)
                .content(jsonMapper.writeValueAsString(userDto)))
                .andExpect(status().isBadRequest());

        verifyNoInteractions(userService);
    }

    @Test
    void create_should_return_bad_request_when_request_is_missing_email() throws Exception {
        // given
        final String PASSWORD = "test_password";
        final UserDto userDto = buildDto(null, PASSWORD);

        // when

        // then
        mockMvc.perform(post(UserAPI.BASE_URL)
                .contentType(MediaType.APPLICATION_JSON)
                .content(jsonMapper.writeValueAsString(userDto)))
                .andExpect(status().isBadRequest());

        verifyNoInteractions(userService);
    }

    @Test
    void create_should_return_bad_request_when_request_is_missing_password() throws Exception {
        // given
        final String EMAIL = "test@test.com";
        final UserDto userDto = buildDto(EMAIL, null);

        // when

        // then
        mockMvc.perform(post(UserAPI.BASE_URL)
                .contentType(MediaType.APPLICATION_JSON)
                .content(jsonMapper.writeValueAsString(userDto)))
                .andExpect(status().isBadRequest());

        verifyNoInteractions(userService);
    }

    private UserDto buildDto(String email, String password) {
        UserDto userDto = new UserDto();
        userDto.setEmail(email);
        userDto.setPassword(password);
        return userDto;
    }

    private User buildUser(String email, String password){
        User user = new User();
        user.setId(1);
        user.setEmail(email);
        user.setPassword(password);
        return user;
    }

}

I've already enabled the filter in my security config and everything is working as expected:

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


来源:https://stackoverflow.com/questions/59776298/custom-filter-breaking-webmvctest-out-of-nowhere

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!