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.
You should not add the FilterChainProxy directly. Instead, you should apply
SecurityMockMvcConfigurers.springSecurity()
as indicated by the reference. An example is included below:The result of this is:
FilterChainProxy
is added as aFilter
toMockMvc
(as you did)TestSecurityContextHolderPostProcessor
is addedWhy is
TestSecurityContextHolderPostProcessor
necessary? The reason is that we need to communicate the current user from the test method to theMockHttpServletRequest
that is created. This is necessary because Spring Security'sSecurityContextRepositoryFilter
will override any value onSecurityContextHolder
to be the value found by the currentSecurityContextRepository
(i.e. theSecurityContext
inHttpSession
).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):
Alternatively
You in Spring Security 4.0.2+ you can use:
Okay, figured it out.
It works now.