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 .