Home > front end >  integration tests failing with 401 error for secure endpoints. Mockmvc not able to authenticate
integration tests failing with 401 error for secure endpoints. Mockmvc not able to authenticate

Time:06-24

I'm writing integration tests for secured endpoints and they are all failing with 401 response. I am using JWT. for some reason mockmvc is not able to authenticate someone could help on what the issue is. here is the code

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class RestEndpointsIntegrationTest extends AbstractIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    protected WebApplicationContext wac;

    @Autowired
    private FilterChainProxy springSecurityFilterChain;
    
    @BeforeEach
    public void setUp() {
        this.mockMvc = MockMvcBuilders
                .webAppContextSetup(wac)
                .apply(springSecurity(springSecurityFilterChain))
                .build();
    }

    @Test
    void getAllTransactionsSuccessTest() throws Exception {
        this.mockMvc.perform(get("/transactions/account?account_id=1")
            //.with(user(AuthenticatedUser.builder().role("API_ADMIN").username("admin_user").build())))
             .with(authentication(new UsernamePasswordAuthenticationToken(
                "admin_user",
                null,
                Collections.singletonList(new SimpleGrantedAuthority("API_ADMIN"))
            ))))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(MockMvcResultMatchers.jsonPath("$.totalElements").value(50))
            .andExpect(MockMvcResultMatchers.jsonPath("$.transactions").exists());

    }

}

I have tried using @MockUser as well and still getting the 401 response

secuirty config class

@Configuration
@EnableWebSecurity
@EnableAutoConfiguration
public class WebSecurityConfig
extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtAuthenticationEntryPoint unauthorizedHandler;
    @Autowired
    private JwtAuthenticationProvider authenticationProvider;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        ((HttpSecurity)((HttpSecurity)((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl)((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl)((HttpSecurity)httpSecurity
                .csrf().disable()).authorizeRequests()
                .antMatchers("/transactions/all").hasRole("API_ADMIN")
                .antMatchers(new String[]{"/auth/token","/v2/api-docs", "/configuration/ui","/swagger-resources", "/configuration/security", "/swagger-ui.html","/api/swagger-ui.html",
                    "/webjars/**", "/swagger-resources/**","/favicon.ico","/**/*.png","/**/*.gif","/**/*.svg","/**/*.jpg","/**/*.html","/**/*.css","/**/*.js"})).permitAll()
                .anyRequest()).authenticated().and()).exceptionHandling()
                .authenticationEntryPoint((AuthenticationEntryPoint)this.unauthorizedHandler)
                .and()).sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        httpSecurity.cors();
        httpSecurity.addFilterBefore((Filter)this.authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
        httpSecurity.headers().cacheControl();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManager() throws Exception {
        return new ProviderManager(Arrays.asList(new AuthenticationProvider[]{this.authenticationProvider}));
    }

    @Bean
    public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
        JwtAuthenticationTokenFilter authenticationTokenFilter = new JwtAuthenticationTokenFilter();
        authenticationTokenFilter.setAuthenticationManager(this.authenticationManager());
        authenticationTokenFilter
                .setAuthenticationSuccessHandler((AuthenticationSuccessHandler)new JwtAuthenticationSuccessHandler());
        return authenticationTokenFilter;
    }
}

CodePudding user response:

I see you use @Autowired for getting autoconfiguring MockMvc instance, but in a same time you configure it self in setUp()

You must remove self configuration for mockMvc and must use @WithMockUser for using default test user configuration for tests (you can see defaults in WithMockUser.class)

@WithMockUser
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class RestEndpointsIntegrationTest extends AbstractIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    protected WebApplicationContext wac;

    @Autowired
    private FilterChainProxy springSecurityFilterChain;

    @Test
    void getAllTransactionsSuccessTest() throws Exception {
        this.mockMvc.perform(get("/transactions/account?account_id=1"))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(MockMvcResultMatchers.jsonPath("$.totalElements").value(50))
            .andExpect(MockMvcResultMatchers.jsonPath("$.transactions").exists());

    }

}

if you need use different user attributes for testing you can specify @WithMockUser self attributes (like username, password etc.)

Required dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
    <scope>test</scope>
</dependency>

CodePudding user response:

  1. Other response is right on the need to remove your setUp() method (@Authowired MockMvc is enough with @AutoConfigureMockMvc)
  2. You should not use @WithMockUser (which populates test security context with UsernamePasswordAuthenticationToken instance, but either
  1. If using @SpringBootTest, like you do, security config should be active, but if using @WebMvcTest, you'll probably need to @Import or @ComponentScan it

You'll find complete usage sample here

Sample unit test with the two flavors (annotation and MockMvc post processor to help you choose):

@WebMvcTest(GreetingController.class)
@AutoConfigureSecurityAddons
@Import(SampleApi.WebSecurityConfig.class)
class GreetingControllerAnnotatedTest {

    @Autowired
    MockMvc api;

    @Test
    @WithMockJwtAuth(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = @OpenIdClaims(sub = "Ch4mpy", preferredUsername = "Tonton Pirate"))
    void greetWithAnnotation() throws Exception {
        api.perform(get("/greet")).andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."));
    }

    @Test
    void greetWithPostProcessor() throws Exception {
        api.perform(get("/greet/ch4mpy").secure(true).with(SecurityMockMvcRequestPostProcessors.jwt()
                .authorities(List.of(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL"))).jwt(jwt -> {
                    jwt.subject("Ch4mpy");
                    jwt.claims(claims -> claims.put(StandardClaimNames.PREFERRED_USERNAME, "Tonton Pirate"));
                }))).andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."));
    }
  • Related