Testing Spring Boot Security simply
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 aFilter
toMockMvc
(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.
Jakub Kozłowski
Updated on July 09, 2022Comments
-
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 over 8 yearsSpring 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 over 8 yearsThe 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"))).andExpect(status().isOk()); doesn't work either. I totally don't get it
-
Jakub Kozłowski over 8 yearsThanks. 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 over 8 yearsCan you provide a link to a new question or update your configuration?
-
Rob Winch over 8 yearsActually, I figured it out. See the udpate
-
Jakub Kozłowski over 8 yearsI am indeed using authorities in my config, but changing
roles
toauthorities
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 over 8 yearsPlease post new question with all the details. You can post link in comments and I'll respond
-
Jakub Kozłowski over 8 yearsstackoverflow.com/questions/31818729/… P.S. you're awesome ;)
-
lrkwz almost 8 yearsWhere is the static user() defined (Eclipse fails totally on including static)
-
Jakub Kozłowski almost 8 yearsHey, it's
org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
, most likely from thespring-security-test
library. -
valijon about 6 years
.with(user("user"))
ignores any kind of permission or role based security