0

I'm developing Spring boot and security web application with security that is WebSecurityConfigurerAdapter based. This application has two WebSecurityConfigurerAdapters for two authorization types - login form and bearer with JWT.
There are some simple rest controllers with JWT protected endpoints. WebSecurityConfigurerAdapter implementation for the bearer with JWT is as follows:

@Configuration
@Order(2)
public class SecurityConfigRest extends WebSecurityConfigurerAdapter {


    protected void configure(HttpSecurity http) throws Exception {

        http.requestMatcher(new AntPathRequestMatcher("/rest/**")).authorizeRequests()
                .regexMatchers(HttpMethod.POST,"/rest/products/add/?").hasRole("ADMIN")
                .antMatchers(HttpMethod.GET,"/rest/products/store/**").hasRole("ADMIN")
                .regexMatchers(HttpMethod.POST,"/rest/store/add/?").hasRole("ADMIN")
                .regexMatchers(HttpMethod.POST,"/rest/store/\\d/brands/?").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
                .oauth2ResourceServer().jwt() 
                .jwtAuthenticationConverter(jwtAuthenticationConverter()); // - simple custom converter
    }
    
}

I'm creating a unit test for those JWT protected endpoints:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.jwt.Jwt;

import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;

import java.math.BigDecimal;
import java.net.URI;
import java.util.Collections;
import java.util.Optional;

import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

@RunWith(SpringRunner.class)
@WebMvcTest(ProductControllerRest.class )
@ContextConfiguration(classes = WebMwcTestConfig.class)
@ActiveProfiles("test")
public class ProductControllerRestIntegrationTest {

    @Autowired
    private MockMvc mockMvc;



    @Test
    public void testProductAdd() throws Exception {

  
        String scheme = env.getProperty(SERVER_SSL_ENABLED, Boolean.class, Boolean.TRUE) ? "https" : "http";
        String port   = Optional.ofNullable(env.getProperty(SERVER_PORT))
                .map(":"::concat).orElse("");
        StringBuilder uriBuilder = new StringBuilder(scheme).append("://").append("localhost").append(port)
                .append("/rest/products/add/");
        URI uri = URI.create(uriBuilder.toString());

        MvcResult mvcResult = mockMvc.perform(post(uri)
                .secure(true)
                .accept(MediaType.APPLICATION_JSON)
                .header(HttpHeaders.AUTHORIZATION, "Bearer test-token")
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .content(getStringJson(productDto))).andReturn();
        verify(productRepository).save(productCaptor.capture());
        ...... 
    }
}

JavaConfig contains aplication context moks for WebSecurityConfigurerAdapter and JWT converter/decoder for the spring security filter chain:

import local.authorization.resource.server.controller.rest.ProductControllerRest;
import local.authorization.resource.server.security.SecurityConfigRest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

import java.util.Arrays;
import java.util.Collections;

@Configuration
public class WebMwcTestConfig {

    @Bean
    public SecurityConfigRest securityConfigRest() {
        return new SecurityConfigRest();
    }

    @Bean  // - add JwtDecoder mock to application context to be used in JwtAuthenticationProvider
    public JwtDecoder jwtDecoderAdmin() { 
        return (token) -> createJwtToken("testAdmin", "ROLE_ADMIN");
    }



    @Bean
    public UserDetailsService userDetailsService() {

        User basicUserTest = new User("testUser","testUserPass", Arrays.asList(
                new SimpleGrantedAuthority("USER")
        ));

        User managerActiveUser = new User("testAdmin","testAdminPass", Arrays.asList(
                new SimpleGrantedAuthority("ADMIN")
        ));

        return new InMemoryUserDetailsManager(Arrays.asList(
                basicUserTest, managerActiveUser
        ));
    }

    private Jwt createJwtToken(String userName, String role) {
        String userId = "AZURE-ID-OF-USER";
        String applicationId = "AZURE-APP-ID";

        return Jwt.withTokenValue("test-token")
                .header("typ", "JWT")
                .header("alg", "none")
                .claim("oid", userId)
                .claim("user_name", userName)
                .claim("azp", applicationId)
                .claim("ver", "1.0")
                .claim("authorities", Collections.singletonList(role))
                .subject(userId)
                .build();
    }
}

But after MockHttpServletRequest is passing the spring security filter chain and reaches rest controller, MockMvc returns MockHttpServletResponse with 404 status:

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /rest/products/add/
       Parameters = {}
          Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json", Authorization:"Bearer test-token", Content-Length:"125"]
             Body = {
  "name" : "test_product_1_name",
  "description" : "test_product_1_description",
  "price" : 0.1,
  "storeId" : 100
}
    Session Attrs = {}
Handler:
             Type = org.springframework.web.servlet.resource.ResourceHttpRequestHandler
Async:
    Async started = false
     Async result = null
Resolved Exception:
             Type = null
ModelAndView:
        View name = null
             View = null
            Model = null
FlashMap:
       Attributes = null
MockHttpServletResponse:
           Status = 404
    Error message = null
          Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-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", Strict-Transport-Security:"max-age=31536000 ; includeSubDomains", X-Frame-Options:"DENY"]
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

It seems that the reason is that ProductControllerRest hasn't been mocked out and added to the application context by @WebMvcTest(controllers = ProductControllerRest.class). Here is indicated that @WebMvcTest is the right way for controllers mocking.

Another oprinon:

@SpringBootTest(webEnvironment = MOCK)
@AutoConfigureMockMvc

is resulting in the same - being able to pass security chain, MockHttpServletResponse status is still 404

I was trying also :

@Autowired
private WebApplicationContext webAppContext;
mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext).build(); // - init mockMvc with webAppContext Autowired

In this case security filter chain isn't being passed.

Are there other configurations and options how to mock a rest controller out with @WebMvcTest or @SpringBootTest?

Dmitry
  • 11
  • 4

0 Answers0