Testing Spring Boot Security simply

24,086

Solution 1

You should not add the FilterChainProxy directly. Instead, you should apply SecurityMockMvcConfigurers.springSecurity() as indicated by the reference. An example is included below:

mockMvc = MockMvcBuilders
            .webAppContextSetup(context)
            .apply(springSecurity())
            .build();

The result of this is:

  • the FilterChainProxy is added as a Filter to MockMvc (as you did)
  • the TestSecurityContextHolderPostProcessor is added

Why is TestSecurityContextHolderPostProcessor necessary? The reason is that we need to communicate the current user from the test method to the MockHttpServletRequest that is created. This is necessary because Spring Security's SecurityContextRepositoryFilter will override any value on SecurityContextHolder to be the value found by the current SecurityContextRepository (i.e. the SecurityContext in HttpSession).

Update

Remember anything that contains role in the method name automatically prefixes "ROLE_" to the string that was passed in.

Based on your comment, the problem is you need to either update your configuration to use hasRole instead of hasAuthority (since your annotation is using roles):

.authorizeRequests()
            .antMatchers("/api/user/**", "/user").authenticated()
            .antMatchers("/api/admin/**", "/templates/admin/**", "/admin/**").hasRole("ADMIN")
            .anyRequest().permitAll();

Alternatively

You in Spring Security 4.0.2+ you can use:

@WithMockUser(authorities="ADMIN")

Solution 2

Okay, figured it out.

mockMvc.perform(get("/api/user/account")
      .with(user("user")))
      .andExpect(status().isOk());

It works now.

Share:
24,086
Jakub Kozłowski
Author by

Jakub Kozłowski

Updated on July 09, 2022

Comments

  • Jakub Kozłowski
    Jakub Kozłowski almost 2 years

    I'm struggling with testing access control on URLs protected by Spring Security.

    The configuration looks like this:

        http
                .authorizeRequests()
                .antMatchers("/api/user/**", "/user").authenticated()
                .antMatchers("/api/admin/**", "/templates/admin/**", "/admin/**").hasAuthority("ADMIN")
                .anyRequest().permitAll();
    

    And the test class looks like this:

    package com.kubukoz.myapp;
    
    import com.kubukoz.myapp.config.WebSecurityConfig;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.SpringApplicationConfiguration;
    import org.springframework.security.web.FilterChainProxy;
    import org.springframework.test.context.ActiveProfiles;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.springframework.test.context.transaction.TransactionConfiguration;
    import org.springframework.test.context.web.WebAppConfiguration;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    import org.springframework.web.context.WebApplicationContext;
    
    import javax.transaction.Transactional;
    
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes = {MyApplication.class, WebSecurityConfig.class})
    @WebAppConfiguration
    @TransactionConfiguration(defaultRollback = true)
    @Transactional(rollbackOn = Exception.class)
    public class MyApplicationTests {
    
        @Autowired
        private WebApplicationContext context;
        private MockMvc mockMvc;
        @Autowired
        private FilterChainProxy filterChainProxy;
    
        @Before
        public void setUp() {
            mockMvc = MockMvcBuilders.webAppContextSetup(context)
                    .dispatchOptions(true)
                    .addFilters(filterChainProxy)
                    .build();
        }
    
        @Test
        public void testAnonymous() throws Exception {
            mockMvc.perform(get("/api/user/account")).andExpect(status().is3xxRedirection());
        }
    
        @Test
        public void testUserAccessForAccount() throws Exception{
            mockMvc.perform(get("/api/user/account")).andExpect(status().isOk());
        }
    }
    

    What's the easiest way to make the last two tests pass? @WithMockUser didn't work.

  • Roman Vottner
    Roman Vottner over 8 years
    Spring Security allows to specify the actual user of a request in multiple ways. You should also be able to annotate the test method with something like @WithMockUser(roles="ADMIN") as explained in the referenced documentation
  • Jakub Kozłowski
    Jakub Kozłowski over 8 years
    The thing is - @WithMockUser(roles = "USER") didn't work with the method, contrary to with(user(...)). Also, mockMvc.perform(get("/admin/health").with(user("user").roles‌​("ADMIN"))).andExpec‌​t(status().isOk()); doesn't work either. I totally don't get it
  • Jakub Kozłowski
    Jakub Kozłowski over 8 years
    Thanks. Now I can use with(user("...").roles("ADMIN")) - but a @WithMockUser(roles="ADMIN") call to (admin role protected) /admin/health still gives me a 403, as if the roles parameter was ignored :/
  • Rob Winch
    Rob Winch over 8 years
    Can you provide a link to a new question or update your configuration?
  • Rob Winch
    Rob Winch over 8 years
    Actually, I figured it out. See the udpate
  • Jakub Kozłowski
    Jakub Kozłowski over 8 years
    I am indeed using authorities in my config, but changing roles to authorities here didn't help - getting a 302 to /login this time. I have a gradle dependency for spring-security-web:4.0.2.RELEASE.
  • Rob Winch
    Rob Winch over 8 years
    Please post new question with all the details. You can post link in comments and I'll respond
  • Jakub Kozłowski
    Jakub Kozłowski over 8 years
  • lrkwz
    lrkwz almost 8 years
    Where is the static user() defined (Eclipse fails totally on including static)
  • Jakub Kozłowski
    Jakub Kozłowski almost 8 years
    Hey, it's org.springframework.security.test.web.servlet.request.Securi‌​tyMockMvcRequestPost‌​Processors.user, most likely from the spring-security-test library.
  • valijon
    valijon about 6 years
    .with(user("user")) ignores any kind of permission or role based security