@WithMockUser doesn't pick Spring Security auth credentials
There are two reasons behind this behavior:
@WithMockUser
annotation is not intended to execute authentication. It creates a user which is authenticated already. By default his credentials areuser
:password
@WebMvcTest
does not execute MySecurityConfig.java. This annotation creates Spring mockMvc object with Security defaults for testing. Those security defaults are applied byorg.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityAutoConfiguration
You can double check this by putting break points onMySecurityConfig
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"));
}
}
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(...);
}