🌞

Stand-alone testing a Spring security configuration

Many examples of testing Spring Security configurations out there focus on how to write an integration test covering your security configuration as well as your controller(s). This post examines one possible way of testing a security configuration in a (mostly) isolated fashion.

In a recent project I ran into a situation which had a relatively complex Spring Security configuration as well as some authorization logic sprinkled across it’s controllers. In order to gain more confidence in refactoring the security configuration, I wanted an isolated test telling me whether my modifications also changed the configuration’s behaviour. I came up with what’s documented in this post.


Let’s start out by looking at the Spring Security configuration we’ll be testing as part of this article:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .formLogin()
            .and()
        .logout()
            .and()
        .authorizeRequests()
            .antMatchers("/secure/**")
                .authenticated()
            .antMatchers("/admin/**")
                .hasRole("ADMIN")
            .anyRequest()
                .permitAll();
}

Here we are setting up a form login and a logout page, and then proceed to say that there are three separate areas:

  • A “secure area”, consisting of all sub-pages of http://example.com/secure. All logged in users can see this content
  • An admin area, containing all sub-pages of http://example.com/admin. Only users with the ADMIN role can see this content
  • Everything else, which is world accessible

In order to test this configuration, I first created a dummy controller in my test code, which catches all GET requests:

@Controller
public class DummyCatchAllController {
    @RequestMapping(path = "/**", method = RequestMethod.GET)
    @ResponseBody
    public String catchAll() {
        return "DummyCatchallController#catchAll";
    }
}

Then I proceeded to set up a test which, using this dummy controller, tests the security configuration. First the boilerplate:

@RunWith(SpringJUnit4ClassRunner.class)
@WebMvcTest(DummyCatchAllController.class)
@Import({SecurityConfiguration.class})
public class SecurityConfigurationTest {
    private MockMvc mockMvc;
    @Autowired private WebApplicationContext webApplicationContext;

    @Before
    public void setup() {
        this.mockMvc =
            MockMvcBuilders
                .webAppContextSetup(this.webApplicationContext)
                .apply(springSecurity())
                .build();
    }

    [...]
}

Instead of @SpringBootTest (loads your full application context) we’re using @WebMvcTest (only loads whatever the controller needs) here, because they are commonly faster[1] and we’re trying to isolate the security configuration as much as possible. Since WebMvcTests don’t load the entire application context though, we need to manually import the security configuration with

@Import({SecurityConfiguration.class})

Next up, in the setup method, we’re initializing MockMVC, which will help us perform our test requests against the controller. We cannot use the @AutoConfigureMockMvc annotation here, because we need to make sure that springSecurity() is applied, since that’s what we want to test.

Now that we’ve got the setup finished, we can finally look at the actual tests. If you’re unfamiliar with how MockMVC works, and the examples are unclear, I recommend looking at this introduction.

First, let’s make sure that our world-accessible resources can be seen by any user:

@Test
public void asAnonymous_ICanAccessPublicResources() throws Exception {
    mockMvc.perform(get("/foobar")).andExpect(status().isOk());
    mockMvc.perform(get("/")).andExpect(status().isOk());
}

Next, let’s see if a loggged in user can see the “secure area”. Here we’re using the @WithMockUser annotation to simulate a logged-in user who has the USER role.

@Test
@WithMockUser(username = "user", roles = {"USER"})
public void asLoggedInUser_ICanAccessSecureArea() throws Exception {
    mockMvc.perform(get("/secure/foobar")).andExpect(status().isOk());
    mockMvc.perform(get("/secure/")).andExpect(status().isOk());
}

At the same time, we also want to make sure that a normal user can’t see the admin area:

@Test
@WithMockUser(username = "user", roles = {"USER"})
public void asLoggedInUser_ICantAccessAdminArea() throws Exception {
    mockMvc.perform(get("/admin/foobar")).andExpect(status().isForbidden());
    mockMvc.perform(get("/admin/")).andExpect(status().isForbidden());
}

Finally, an admin should be able to see the admin area:

@Test
@WithMockUser(username = "admin", roles = {"USER", "ADMIN"})
public void asAdminUser_ICanAccessAdminArea() throws Exception {
    mockMvc.perform(get("/admin/foobar")).andExpect(status().isOk());
    mockMvc.perform(get("/admin/")).andExpect(status().isOk());
}

Note that the usernames that we’re supplying to the @WithMockUser annotation in all of these examples does not actually have any meaning. I’ve only added it to make the test cases clearer.

That was it already. If you’re interested in a runnable example to play around with, you can find it here.


Feedback is always welcome and I try to incorporate it into the post where possible. For reasons of Datensparsamkeit I do not provide a commenting solution here, however. You can reach me via email (see home page) or Twitter.

  1. See https://stackoverflow.com/a/39869110/124257 for more info