Spring Test returning 401 for unsecured URLs

后端 未结 4 1579
北荒
北荒 2020-12-07 00:38

I am using Spring for MVC tests

Here is my test class

@RunWith(SpringRunner.class)
@WebMvcTest
public class ITIndexController {

    @Autowired
    W         


        
相关标签:
4条回答
  • 2020-12-07 01:08

    If you use SpringJUnit4ClassRunner instead of SpringRunner you can catch your requests in security layer. If you are using basic authentication you have to user httpBasic method inside mockMvc.perform

     mockMvc.perform(get("/").with(httpBasic(username,rightPassword))
    
    0 讨论(0)
  • 2020-12-07 01:18

    Not sure if this was available when the original question was asked, but if truly not wanting to test the security portion of a web request (which seems reasonable if the endpoint is known to be unsecure), then I think this could be done simply by using the secure attribute of the @WebMvcTest annotation (it defaults to true so setting it to false should disable the auto-configuration of Spring Security's MockMvc support):

    @WebMvcTest(secure = false)
    

    More info available in the javadocs

    0 讨论(0)
  • 2020-12-07 01:19

    I found the answer
    Spring docs says that:

    @WebMvcTest will auto-configure the Spring MVC infrastructure and limit scanned beans to @Controller, @ControllerAdvice, @JsonComponent, Filter, WebMvcConfigurer and HandlerMethodArgumentResolver. Regular @Component beans will not be scanned when using this annotation.

    And according to this issue in github:

    https://github.com/spring-projects/spring-boot/issues/5476

    The @WebMvcTest by default auto configure spring security if spring-security-test is present in the class path (which in my case is).

    So since WebSecurityConfigurer classes aren't picked, the default security was being auto configured, that is the motive I was receiving the 401 in url's that was not secured in my security configuration. Spring security default auto configuration protects all url's with basic authentication.

    What I did to solve the problem was to annotate the class with @ContextConfiguration, and @MockBean like it is described in the documentation:

    Often @WebMvcTest will be limited to a single controller and used in combination with @MockBean to provide mock implementations for required collaborators.

    And here is the test class

    @RunWith(SpringRunner.class)
    @WebMvcTest
    @ContextConfiguration(classes={Application.class, MvcConfig.class, SecurityConfig.class})
    public class ITIndex {
    
        @Autowired
        WebApplicationContext context;
    
        MockMvc mockMvc;
    
        @MockBean
        UserRegistrationApplicationService userRegistrationApplicationService;
    
        @MockBean
        UserDetailsService userDetailsService;
    
        @Before
        public void setUp() {
            this.mockMvc = MockMvcBuilders
                            .webAppContextSetup(context)
                            .apply(springSecurity())
                            .build();
        }
    
        @Test
        public void should_render_index() throws Exception {
            mockMvc.perform(get("/"))
                .andExpect(status().isOk())
                .andExpect(view().name("index"))
                .andExpect(content().string(containsString("Login")));
        }
    }
    

    Application, MvcConfig and SecurityConfig are all my configuration classes

    0 讨论(0)
  • 2020-12-07 01:22

    I had an some problem and solve the issue with the help of the answers here and @Sam Brannen comment.

    You probably don't need to use @ContextConfiguration. Simply adding @Import(SecurityConfig.class) should typically suffice.

    To simplify and update the answers a bit more I want to share how i fix it in my spring-boot2 project.

    I want to test below endpoint.

    @RestController
    @Slf4j
    public class SystemOptionController {
    
      private final SystemOptionService systemOptionService;
      private final SystemOptionMapper systemOptionMapper;
    
      public SystemOptionController(
          SystemOptionService systemOptionService, SystemOptionMapper systemOptionMapper) {
        this.systemOptionService = systemOptionService;
        this.systemOptionMapper = systemOptionMapper;
      }
    
      @PostMapping(value = "/systemoption")
      public SystemOptionDto create(@RequestBody SystemOptionRequest systemOptionRequest) {
        SystemOption systemOption =
            systemOptionService.save(
                systemOptionRequest.getOptionKey(), systemOptionRequest.getOptionValue());
        SystemOptionDto dto = systemOptionMapper.mapToSystemOptionDto(systemOption);
        return dto;
      }
    }
    

    All service methods must be interface otherwise application context can't be initialized. You can check my SecurityConfig.

    @Configuration
    @EnableWebSecurity
    @EnableResourceServer
    @EnableGlobalMethodSecurity(securedEnabled = true)
    public class SecurityConfig extends ResourceServerConfigurerAdapter {
    
        @Autowired
        private ResourceServerTokenServices resourceServerTokenServices;
    
        @Override
        public void configure(final HttpSecurity http) throws Exception {
            if (Application.isDev()) {
                http.csrf().disable().authorizeRequests().anyRequest().permitAll();
            } else {
                http
                        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                        .and()
                        .authorizeRequests().regexMatchers("/health").permitAll()
                    .antMatchers("/prometheus").permitAll()
                    .anyRequest().authenticated()
                        .and()
                        .authorizeRequests()
                        .anyRequest()
                        .permitAll();
                http.csrf().disable();
            }
        }
    
        @Override
        public void configure(final ResourceServerSecurityConfigurer resources) {
            resources.tokenServices(resourceServerTokenServices);
        }
    }
    

    And below you can see my SystemOptionControllerTest class.

    @RunWith(SpringRunner.class)
    @WebMvcTest(value = SystemOptionController.class)
    @Import(SecurityConfig.class)
    public class SystemOptionControllerTest {
    
      @Autowired private ObjectMapper mapper;
    
      @MockBean private SystemOptionService systemOptionService;
      @MockBean private SystemOptionMapper systemOptionMapper;
      @MockBean private ResourceServerTokenServices resourceServerTokenServices;
    
      private static final String OPTION_KEY = "OPTION_KEY";
      private static final String OPTION_VALUE = "OPTION_VALUE";
    
      @Autowired private MockMvc mockMvc;
    
      @Test
      public void createSystemOptionIfParametersAreValid() throws Exception {
        // given
    
        SystemOption systemOption =
            SystemOption.builder().optionKey(OPTION_KEY).optionValue(OPTION_VALUE).build();
    
        SystemOptionDto systemOptionDto =
            SystemOptionDto.builder().optionKey(OPTION_KEY).optionValue(OPTION_VALUE).build();
    
        SystemOptionRequest systemOptionRequest = new SystemOptionRequest();
        systemOptionRequest.setOptionKey(OPTION_KEY);
        systemOptionRequest.setOptionValue(OPTION_VALUE);
        String json = mapper.writeValueAsString(systemOptionRequest);
    
        // when
        when(systemOptionService.save(
                systemOptionRequest.getOptionKey(), systemOptionRequest.getOptionValue()))
            .thenReturn(systemOption);
        when(systemOptionMapper.mapToSystemOptionDto(systemOption)).thenReturn(systemOptionDto);
    
        // then
        this.mockMvc
            .perform(
                post("/systemoption")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(json)
                    .accept(MediaType.APPLICATION_JSON))
            .andDo(print())
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(content().string(containsString(OPTION_KEY)))
            .andExpect(content().string(containsString(OPTION_VALUE)));
      }
    }
    

    So I just need to add @Import(SecurityConfig.class) to my mvc test class.

    0 讨论(0)
提交回复
热议问题