@WithMockUser doesn't pick Spring Security auth credentials

19,285

Solution 1

There are two reasons behind this behavior:

  1. @WithMockUser annotation is not intended to execute authentication. It creates a user which is authenticated already. By default his credentials are user : password
  2. @WebMvcTest does not execute MySecurityConfig.java. This annotation creates Spring mockMvc object with Security defaults for testing. Those security defaults are applied by org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityAutoConfiguration You can double check this by putting break points on MySecurityConfig methods and rerunning your test in debug mode. Break points are not hit.

Solving issue 1

Simply change your approach to what @WithMockUser annotation does. It gives already logged-in user. It is still possible to test urls security and roles configuration with specifying concrete username, password and roles.

Solving issue 2
Create a base class for all Integration tests. It will configure mockMvc with Spring Security applied. Also note @SpringBootTest annotation. Now test will use MySecurityConfig.java

import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;

import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringRunner.class)
@SpringBootTest
public abstract class IT {
    @Autowired
    protected WebApplicationContext wac;

    @Autowired
    private FilterChainProxy springSecurityFilterChain;

    protected MockMvc mockMvc;

    @Before
    public void applySecurity() {
        this.mockMvc = webAppContextSetup(wac)
            .apply(springSecurity(springSecurityFilterChain))
            .build();
    }
}

Rewrite the test like this. Assuming you use http basic authentication. Credentials are provided inside the test. Note: no mock user annotation.

package com.example.demo;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;

import org.junit.Test;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

public class SomeControllerIT extends IT {

  @Test
  public void test1() throws Exception {
    mockMvc.perform(get("/some")
        .with(httpBasic("user", "user")))
        .andExpect(MockMvcResultMatchers.content().string("hello"));
  }
}

Solution 2

Here is how you can run your mockMVC tests with your configuration of spring security: for the USER role...

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = SomeController.class)
public class SomeControllerTest {

    @Autowired
    private WebApplicationContext context;

    @Autowired
    private MockMvc mockMvc;

    @Before
    public void setup() {
      mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .defaultRequest(get("/")
        .with(user("user").password("password").roles("USER")))
        .apply(springSecurity())
        .build();
    }


    @Test
    public void test1() {
                  mockMvc.perform(get(...)).andExpect(...);
    }
}

after making this change your GET tests should now work.

since spring security provides cross site request forgery protection for http requests such as POST and DELETE, you need to run these particular tests with crsf()

@Test
    public void shouldPost() {
                  mockMvc.perform(post(...)).with(csrf().asHeader())
                         .andExpect(...);
    }

Solution 3

I faced the same issue. I could resolve it by with the @WithMockUser annotation with authorities specified.

@Test
@DisplayName("Should create and return the student")
@WithMockUser(username = "admin", authorities = { "ADMIN", "USER" })
public void should_create_student() throws Exception {
     mockMvc.perform(post(...)).andExpect(...);
}
Share:
19,285
Johan
Author by

Johan

Updated on June 21, 2022

Comments

  • Johan
    Johan almost 2 years

    I have setup basic authentication in my controller with Spring Security in the classic way as follows:

    @EnableWebSecurity
    @EnableGlobalMethodSecurity
    public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    
    
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                    .withUser("user").password("user").roles("USER")
                    .and()
                    .withUser("admin").password("admin").roles("USER", "ADMIN");
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers(....);
        }
    }
    

    When it comes to the point of testing, I am using @WithMockUser to annotate my tests. A test might look like this for GET:

    @RunWith(SpringRunner.class)
    @WebMvcTest(controllers = SomeController.class)
    public class SomeControllerTest {
    
        @Autowired
        private MockMvc mockMvc;
    
        @Test
        public void test1() {
                      mockMvc.perform(get(...)).andExpect(...);
        }
    

    or like this for POST:

    @RunWith(SpringRunner.class)
    @WebMvcTest(controllers = SomeController.class)
    public class SomeControllerTest {
    
        @Autowired
        private MockMvc mockMvc;
    
        @Test
        public void test1() {
                      mockMvc.perform(post(...)).andExpect(...);
        }
    

    then something unexpected happens:

    • when a test method is not annotated with @WithMockUser, it fails because of 401 status (Unauthorized) which is reasonable, because no basic authentication has been fullfilled
    • when a test method is simply annotated with an empty @WithMockUser without specifying ANY credentials, it starts passing, which is not reasonable, because I did not provide the correct data for authentication (rather I left them empty)
    • at this point a test method is passing also when filling in some correct credentials like in @WithMockUser(username = "user", password = "user", roles = "USER")

    QUESTION: what's going on? How to fix this misbehaviour?

    Looks like Spring Security is activated, however my testing is not using the authentication that I would expect to be used. Do I have to mock the authentication data myself?

    EDIT The full configure method is the following

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers(URL1).hasAnyRole(ROLE_ADMIN)
                .antMatchers(URL2).hasAnyRole(ROLE_USER)
                .antMatchers(URL3).permitAll()
                .and()
                .httpBasic()
                .and()
                .csrf().disable();
    }
    
  • Johan
    Johan over 6 years
    Does not wok. If I use a wrong password, it still passes without problems
  • cblanto7
    cblanto7 over 6 years
    can I see your full SecurityConfig configure method? my thought is that .anyRequest().authenticated() might be missing and that would explain that behavior.
  • Johan
    Johan over 6 years
    Yes, I have added it to the bottom of the question
  • Johan
    Johan over 6 years
    What does .defaultRequest(get("/") mean in your code?
  • cblanto7
    cblanto7 over 6 years
    that is meant to ensure to run as a specific user for every request by using MockMvcBuilders’s default request. see more here docs.spring.io/spring-security/site/docs/4.0.x/reference/…
  • Johan
    Johan over 6 years
    this is the answer