When we implement authorization in Spring Boot with Spring Security, for instance, using the PreAuth annotation, we should never skip automated tests for it. We could use the @PreAuth, among other annotations, to control authenticated users’ access to some methods or even endpoints. However, the ease of usage of these annotations could also mean ease of change. With just a handful of string properties to affect their behaviors, we could inadvertently allow access to restricted resources or operations when we change something. Therefore, we should create tests for codes that use these annotations for authorization.
Spring Boot And Spring Security
Let us say we have a working Spring Boot application with authentication and authorization features. Some of our codes could look like the following. We have a controller with two methods showing all or searching for permissions belonging to a particular tenant. To access the permission data, the current user has to have either permission – find_permission or view_permission.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | package com.turreta.headstart.controller; // Omitted for brevity @RestController @RequestMapping("be/permissions") public class PermissionController { // Omitted for brevity @GetMapping @PreAuthorize("hasAuthority('find_permission')") public ResponseEntity<List<Permission>> getAllPermissions() { List<Permission> allPermissions = permMngtUseCase.getAllPermissions(auth.user()); return ResponseEntity.ok().body(allPermissions); } @GetMapping("/{permissionId}") @PreAuthorize("hasAuthority('view_permission')") public ResponseEntity<List<Permission>> showPermission(@PathVariable("permissionId") String permId) { List<Permission> permissionById = permMngtUseCase.findPermissionById( Permission.builder().id(permId).build(), auth.user()); return ResponseEntity.ok().body(permissionById); } } |
Using the PreAuth and MockWitUser Annotations
We craft integration tests when we create automated tests for Spring Boot authorization (e.g., via PreAuth) that use Spring Security. These tests require interaction with Spring Boot and Spring Security components at runtime. Therefore, we need to load some codes in the Spring context.
The integration tests for our Permission REST controller may look something like the following.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | package com.turreta.headstart.controller; // Omitted for brevity import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mockito; import org.mockito.Spy; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; // Omitted for brevity @SpringBootTest @AutoConfigureMockMvc class PermissionController_Security_IT { @Autowired private MockMvc mockMvc; @MockBean PermissionManagementUseCase permissionManagementUseCase; @MockBean private Auth auth; @Spy @InjectMocks private PermissionController permissionController; @BeforeEach public void before() { Mockito.when(auth.user()).thenReturn(User.builder().id("1").build()); } @Test @WithMockUser(authorities={"find_permission"}) void get_all_permissions_SHOULD_ALLOW() throws Exception { mockMvc.perform(get("/be/permissions") .accept(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isOk()); } @Test @WithMockUser void get_all_permissions_SHOULD_NOT_ALLOW() throws Exception { mockMvc.perform(get("/be/permissions") .accept(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isForbidden()); } @Test @WithMockUser(authorities={"view_permission"}) void show_permission_SHOULD_ALLOW() throws Exception { mockMvc.perform(get("/be/permissions/222") .accept(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isOk()); } @Test @WithMockUser void show_permission_SHOULD_NOT_ALLOW() throws Exception { mockMvc.perform(get("/be/permissions/222") .accept(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isForbidden()); } } |
Note that we are using another annotation – @WithMockUser – to mock a logged-in user with roles or authorities we want to test. For example, a pair of methods try for a user with the view_permission permission and without it. Spring Security has other annotations for a similar purpose. However, for this post, we stick with @WithMockUser.
To use these annotations, we need to use the following Maven dependency along with spring-boot-starter-test, and spring-security to mention a few.
1 2 3 4 5 | <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> |
The codes herein are part of the Headstart Framework we are developing using Spring Boot 2.5.5.