Spring Test & Security: How to mock authentication?

Seaching for answer I couldn’t find any to be easy and flexible at the same time, then I found the Spring Security Reference and I realized there are near to perfect solutions. AOP solutions often are the greatest ones for testing, and Spring provides it with @WithMockUser, @WithUserDetails and @WithSecurityContext, in this artifact:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <version>4.2.2.RELEASE</version>
    <scope>test</scope>
</dependency>

In most cases, @WithUserDetails gathers the flexibility and power I need.

How @WithUserDetails works?

Basically you just need to create a custom UserDetailsService with all the possible users profiles you want to test. E.g

@TestConfiguration
public class SpringSecurityWebAuxTestConfig {

    @Bean
    @Primary
    public UserDetailsService userDetailsService() {
        User basicUser = new UserImpl("Basic User", "[email protected]", "password");
        UserActive basicActiveUser = new UserActive(basicUser, Arrays.asList(
                new SimpleGrantedAuthority("ROLE_USER"),
                new SimpleGrantedAuthority("PERM_FOO_READ")
        ));

        User managerUser = new UserImpl("Manager User", "[email protected]", "password");
        UserActive managerActiveUser = new UserActive(managerUser, Arrays.asList(
                new SimpleGrantedAuthority("ROLE_MANAGER"),
                new SimpleGrantedAuthority("PERM_FOO_READ"),
                new SimpleGrantedAuthority("PERM_FOO_WRITE"),
                new SimpleGrantedAuthority("PERM_FOO_MANAGE")
        ));

        return new InMemoryUserDetailsManager(Arrays.asList(
                basicActiveUser, managerActiveUser
        ));
    }
}

Now we have our users ready, so imagine we want to test the access control to this controller function:

@RestController
@RequestMapping("/foo")
public class FooController {

    @Secured("ROLE_MANAGER")
    @GetMapping("/salute")
    public String saluteYourManager(@AuthenticationPrincipal User activeUser)
    {
        return String.format("Hi %s. Foo salutes you!", activeUser.getUsername());
    }
}

Here we have a get mapped function to the route /foo/salute and we are testing a role based security with the @Secured annotation, although you can test @PreAuthorize and @PostAuthorize as well.
Let’s create two tests, one to check if a valid user can see this salute response and the other to check if it’s actually forbidden.

@RunWith(SpringRunner.class)
@SpringBootTest(
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        classes = SpringSecurityWebAuxTestConfig.class
)
@AutoConfigureMockMvc
public class WebApplicationSecurityTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithUserDetails("[email protected]")
    public void givenManagerUser_whenGetFooSalute_thenOk() throws Exception
    {
        mockMvc.perform(MockMvcRequestBuilders.get("/foo/salute")
                .accept(MediaType.ALL))
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("[email protected]")));
    }

    @Test
    @WithUserDetails("[email protected]")
    public void givenBasicUser_whenGetFooSalute_thenForbidden() throws Exception
    {
        mockMvc.perform(MockMvcRequestBuilders.get("/foo/salute")
                .accept(MediaType.ALL))
                .andExpect(status().isForbidden());
    }
}

As you see we imported SpringSecurityWebAuxTestConfig to provide our users for testing. Each one used on its corresponding test case just by using a straightforward annotation, reducing code and complexity.

Better use @WithMockUser for simpler Role Based Security

As you see @WithUserDetails has all the flexibility you need for most of your applications. It allows you to use custom users with any GrantedAuthority, like roles or permissions. But if you are just working with roles, testing can be even easier and you could avoid constructing a custom UserDetailsService. In such cases, specify a simple combination of user, password and roles with @WithMockUser.

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@WithSecurityContext(
    factory = WithMockUserSecurityContextFactory.class
)
public @interface WithMockUser {
    String value() default "user";

    String username() default "";

    String[] roles() default {"USER"};

    String password() default "password";
}

The annotation defines default values for a very basic user. As in our case the route we are testing just requires that the authenticated user be a manager, we can quit using SpringSecurityWebAuxTestConfig and do this.

@Test
@WithMockUser(roles = "MANAGER")
public void givenManagerUser_whenGetFooSalute_thenOk() throws Exception
{
    mockMvc.perform(MockMvcRequestBuilders.get("/foo/salute")
            .accept(MediaType.ALL))
            .andExpect(status().isOk())
            .andExpect(content().string(containsString("user")));
}

Notice that now instead of the user [email protected] we are getting the default provided by @WithMockUser: user; yet it won’t matter because what we really care about is his role: ROLE_MANAGER.

Conclusions

As you see with annotations like @WithUserDetails and @WithMockUser we can switch between different authenticated users scenarios without building classes alienated from our architecture just for making simple tests. Its also recommended you to see how @WithSecurityContext works for even more flexibility.

Leave a Comment