Перейти к основному содержимому

Spring Security для интеграционных тестов Spring Boot

· 4 мин. чтения

1. Введение

Возможность выполнять интеграционные тесты без необходимости в автономной среде интеграции является ценной функцией для любого стека программного обеспечения. Полная интеграция Spring Boot с Spring Security упрощает тестирование компонентов, взаимодействующих с уровнем безопасности.

В этом кратком руководстве мы рассмотрим использование @MockMvcTest и @SpringBootTest для выполнения интеграционных тестов с поддержкой безопасности.

2. Зависимости

Давайте сначала добавим зависимости, которые нам понадобятся для нашего примера:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

Стартеры spring-boot-starter-web , spring-boot-starter-security и spring-boot-starter-test предоставляют нам доступ к Spring MVC, Spring Security и тестовым утилитам Spring Boot.

Кроме того, мы добавим spring-security-test , чтобы получить доступ к аннотации @WithMockUser , которую мы будем использовать.

3. Конфигурация веб-безопасности

Наша конфигурация веб-безопасности будет простой. Только аутентифицированные пользователи смогут получить доступ к путям, соответствующим /private/** . Пути, соответствующие /public/** , будут доступны любому пользователю:

@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
auth.inMemoryAuthentication()
.passwordEncoder(encoder)
.withUser("spring")
.password(encoder.encode("secret"))
.roles("USER");
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/private/**")
.authenticated()
.antMatchers("/public/**")
.permitAll()
.and()
.httpBasic();
}
}

4. Конфигурация безопасности метода

В дополнение к безопасности на основе пути URL, которую мы определили в нашем WebSecurityConfigurer, мы можем настроить безопасность на основе метода, предоставив дополнительный файл конфигурации:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfigurer
extends GlobalMethodSecurityConfiguration {
}

Эта конфигурация включает поддержку аннотаций до/после Spring Security. Другие атрибуты также доступны, если требуется дополнительная поддержка. Для получения дополнительной информации о Spring Method Security загляните в нашу статью на эту тему .

5. Тестирование контроллеров с помощью @WebMvcTest

При использовании подхода аннотации @WebMvcTest с Spring Security MockMvc автоматически настраивается с необходимой цепочкой фильтров, необходимой для проверки нашей конфигурации безопасности.

Поскольку MockMvc настроен для нас, мы можем использовать @WithMockUser для наших тестов без какой-либо дополнительной настройки:

@RunWith(SpringRunner.class)
@WebMvcTest(SecuredController.class)
public class SecuredControllerWebMvcIntegrationTest {

@Autowired
private MockMvc mvc;

// ... other methods

@WithMockUser(value = "spring")
@Test
public void givenAuthRequestOnPrivateService_shouldSucceedWith200() throws Exception {
mvc.perform(get("/private/hello").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}

Обратите внимание, что использование @WebMvcTest сообщит Spring Boot о необходимости создания экземпляра только веб-слоя, а не всего контекста. Из-за этого тесты контроллера, использующие @WebMvcTest , будут выполняться быстрее, чем при других подходах .

6. Тестирование контроллеров с помощью @SpringBootTest

При использовании аннотации @SpringBootTest для тестирования контроллеров с Spring Security необходимо явно настроить цепочку фильтров при настройке MockMvc .

Использование статического метода springSecurity , предоставляемого SecurityMockMvcConfigurer , является предпочтительным способом сделать это:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SecuredControllerSpringBootIntegrationTest {

@Autowired
private WebApplicationContext context;

private MockMvc mvc;

@Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}

// ... other methods

@WithMockUser("spring")
@Test
public void givenAuthRequestOnPrivateService_shouldSucceedWith200() throws Exception {
mvc.perform(get("/private/hello").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}

7. Тестирование защищенных методов с помощью @SpringBootTest

@SpringBootTest не требует дополнительной настройки для тестирования защищенных методов. Мы можем просто вызывать методы напрямую и использовать @WithMockUser по мере необходимости:

@RunWith(SpringRunner.class)
@SpringBootTest
public class SecuredMethodSpringBootIntegrationTest {

@Autowired
private SecuredService service;

@Test(expected = AuthenticationCredentialsNotFoundException.class)
public void givenUnauthenticated_whenCallService_thenThrowsException() {
service.sayHelloSecured();
}

@WithMockUser(username="spring")
@Test
public void givenAuthenticated_whenCallServiceWithSecured_thenOk() {
assertThat(service.sayHelloSecured()).isNotBlank();
}
}

8. Тестирование с помощью @SpringBootTest и TestRestTemplate

TestRestTemplate — удобный вариант при написании интеграционных тестов для защищенных конечных точек REST.

Мы можем просто автоматически подключить шаблон и установить учетные данные перед запросом защищенных конечных точек:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SecuredControllerRestTemplateIntegrationTest {

@Autowired
private TestRestTemplate template;

// ... other methods

@Test
public void givenAuthRequestOnPrivateService_shouldSucceedWith200() throws Exception {
ResponseEntity<String> result = template.withBasicAuth("spring", "secret")
.getForEntity("/private/hello", String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
}
}

TestRestTemplate является гибким и предлагает множество полезных параметров, связанных с безопасностью. Подробнее о TestRestTemplate читайте в нашей статье на эту тему .

9. Заключение

В этой статье мы рассмотрели несколько способов выполнения интеграционных тестов с поддержкой безопасности.

Мы рассмотрели, как работать с конечными точками mvccontroller и REST, а также с защищенными методами.

Как обычно, весь исходный код примера можно найти на GitHub .