javaspringspring-securityspring-testspring-test-mvc

Spring Security testing Endpoint for user without specific role


I'm writing some integration tests for my Spring MVC Controller.

The controllers are secured by Spring Security.

This is the test class I currently have:

@SpringBootTest(
    webEnvironment = SpringBootTest.WebEnvironment.MOCK,
    classes = GuiBackendApplication.class
)
@AutoConfigureMockMvc
public class ConfigEditorControllerIntegrationTest {

    @Autowired
    private MockMvc mockedMvc;

    @Test
    @WithMockUser(username = "user", password = "password", roles = {"admin"})
    public void adminsCanAccessRuntimeConfig() throws Exception {

        this.mockedMvc.perform(get("/my/custom/api"))
            .andExpect(status().isOk());
    }
}

This test class ensures that admins can access my endpoint. It works fine.

BUT what if I want to test if ONLY users with the admin role can access my endpoint?

I could write a test that uses @WithMockUsers with all the roles I currently have except the admin role. But that would me awful to maintain. I want my test to ensure that only users with the admin role can access my endpoint, regardless of any new roles.

I checked the Spring Reference Docs and didn't find anything about that. Is there a way to achieve that?

Something like this

@Test
@WithMockUser(username = "user", password = "password", roles = {"IS NOT admin"})
public void nonAdminsCannotAccessRuntimeConfig() throws Exception {

    this.mockedMvc.perform(get("/my/custom/api"))
        .andExpect(status().isUnauthorized());
}

Solution

  • Spring Security does not know what roles does your system define. So you have to tell it and test it one by one if you want to have 100% test coverage for all the available roles.

    You can do it easily and in a maintenance way by using JUnit 5 's @ParameterizedTest and configuring MockMvc with the UserRequestPostProcessor with different roles.

    Something like :

    public class ConfigEditorControllerIntegrationTest {
    
        @ParameterizedTest
        @MethodSource
        public void nonAdminsCannotAccessRuntimeConfig(String role) throws Exception {
            mockedMvc.perform(get("/my/custom/api")
                     .with(user("someUser").roles(role)))
                     .andExpect(status().isUnauthorized());
        }
    
        static List<String> nonAdminsCannotAccessRuntimeConfig() {
            return Roles.exclude("admin");
        }
    }
    

    And create a class to maintain all the available roles :

    public class Roles {
    
        public static List<String> all() {
            return List.of("admin", "hr", "developer" , "accountant" , .... , "devops");
        }
    
        public static List<String> exclude(String excludeRole) {
            List<String> result = new ArrayList<>(all());
            result.remove(excludeRole);
            return result;
        }
    
    }