Spring Security Test Environment

To use Spring Security in your unit tests, you need to add spring-security-test to your Spring Boot project.

1
2
3
4
5
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>

This way the contextual configuration of the tests can be combined with Spring Security, and the next few tricks will teach you.

Spring Security Testing

All tests are done under Spring Boot Test, which is supported by the @SpringBootTest annotation.

@WithMockUser

The @WithMockUser annotation helps us mock a user with a default name of user, a default password of password and a default role of USER in the Spring Security security context. When your test method uses this annotation, you can get information about the simulated user by using the following code, which “pretends” that the user user is currently logged in.

1
2
 Authentication authentication = SecurityContextHolder.getContext()
            .getAuthentication();

Of course, you can also customize the username, password, and role as needed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@SneakyThrows
@Test
@WithMockUser(username = "felord",password = "felord.cn",roles = {"ADMIN"})
void updatePassword() {

    mockMvc.perform(post("/user/update/password")
            .contentType(MediaType.APPLICATION_JSON)
            .content("{\n" +
                    "  \"newPassword\": \"12345\",\n" +
                    "  \"oldPassword\": \"12345\"\n" +
                    "}"))
            .andExpect(ResultMatcher.matchAll(status().isOk()))
            .andDo(print());
}

Of course you can tag @WithMockUser to the entire test class so that each test will use the specified user.

@WithAnonymousUser

@WithAnonymousUser is used to simulate a special kind of user, also called anonymous user. If there is a need to test for anonymous users, you can use this annotation directly. It is actually equivalent to @WithMockUser(roles = {"ANONYMOUS"}) , and also to @WithMockUser(authorities = {"ROLE_ANONYMOUS"}) , you should be able to see the difference if you are careful.

@WithUserDetails

While @WithMockUser is a very convenient way to do this, it may not work in all cases. Sometimes you magic up something so that the validation mechanism of the security context changes, like when you customize UserDetails, and this type of annotation doesn’t work well. But users loaded via UserDetailsService are often still reliable. So @WithUserDetails comes in handy.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@SneakyThrows
@Test
@WithUserDetails("felord")
void updatePassword() {

    mockMvc.perform(post("/user/update/password")
            .contentType(MediaType.APPLICATION_JSON)
            .content("{\n" +
                    "  \"newPassword\": \"12345\",\n" +
                    "  \"oldPassword\": \"12345\"\n" +
                    "}"))
            .andExpect(ResultMatcher.matchAll(status().isOk()))
            .andDo(print());
}

When we execute the unit test, the loadUserByUsername method of the UserDetailsService will find the user with the username felord and load it into the security context.

Custom annotations

We can actually also mock @WithMockUser,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@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[] authorities() default {};

   String password() default "password";

   @AliasFor(annotation = WithSecurityContext.class)
   TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD;

}

The key lies in the @WithSecurityContext annotation, we just need to implement factory on it, which is the following code.

1
2
3
4
public interface WithSecurityContextFactory<A extends Annotation> {

   SecurityContext createSecurityContext(A annotation);
}

Here, just do as you do, nothing difficult to demonstrate.

Summary

Today we covered how to unit test when your application is integrated with Spring Security, we can use the annotations provided to mock the user, we can mock the loaded user, or you can even customize it to suit your needs. In fact, if you use JWT, you can add the corresponding request headers or parameters to the Spring MVC Mock test and it will also work fine.

Reference https://felord.cn/spring-security-test.html