Gymterview
middle

Как тестировать Spring Security?

Spring Security предоставляет модуль spring-security-test с инструментами для тестирования аутентификации и авторизации.

Зависимость

Пример
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>

WithMockUser — эмуляция аутентифицированного пользователя

Пример
@WebMvcTest(UserController.class)
class SecuredControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    @WithMockUser(username = "admin", roles = {"ADMIN"})
    void adminShouldAccessAdminEndpoint() throws Exception {
        mockMvc.perform(get("/api/admin/users"))
            .andExpect(status().isOk());
    }

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

    @Test
    void anonymousUserShouldGetUnauthorized() throws Exception {
        mockMvc.perform(get("/api/admin/users"))
            .andExpect(status().isUnauthorized());
    }
}

SecurityMockMvcRequestPostProcessors — настройка безопасности в запросе

Пример
import static org.springframework.security.test.web.servlet.request
    .SecurityMockMvcRequestPostProcessors.*;

@Test
void shouldAuthenticateWithCsrf() throws Exception {
    mockMvc.perform(post("/api/users")
            .with(csrf())
            .with(user("admin").roles("ADMIN"))
            .contentType(MediaType.APPLICATION_JSON)
            .content("{\"name\": \"Иван\"}"))
        .andExpect(status().isCreated());
}

@Test
void shouldAuthenticateWithHttpBasic() throws Exception {
    mockMvc.perform(get("/api/users")
            .with(httpBasic("user", "password")))
        .andExpect(status().isOk());
}

Кастомная аннотация для повторяющихся сценариев

Пример
@Retention(RetentionPolicy.RUNTIME)
@WithMockUser(username = "admin", roles = {"ADMIN"})
public @interface WithAdmin {}

@Retention(RetentionPolicy.RUNTIME)
@WithMockUser(username = "user", roles = {"USER"})
public @interface WithRegularUser {}

// Использование
@Test
@WithAdmin
void shouldAccessAsAdmin() throws Exception {
    mockMvc.perform(get("/api/admin/dashboard"))
        .andExpect(status().isOk());
}

Интеграционный тест с TestRestTemplate

Пример
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class SecurityIntegrationTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void shouldReturnUnauthorizedWithoutCredentials() {
        ResponseEntity<String> response =
            restTemplate.getForEntity("/api/users", String.class);
        assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
    }

    @Test
    void shouldReturnOkWithValidCredentials() {
        ResponseEntity<String> response = restTemplate
            .withBasicAuth("admin", "password")
            .getForEntity("/api/users", String.class);
        assertEquals(HttpStatus.OK, response.getStatusCode());
    }
}

На собеседовании: минимум, который нужно знать — @WithMockUser и csrf(). Частая ошибка — не добавить .with(csrf()) при тестировании POST/PUT/DELETE-запросов, из-за чего тест падает с 403 Forbidden.