«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 ознакомьтесь с нашей статьей на эту тему.
5. Тестирование контроллеров с помощью @WebMvcTest
При использовании подхода аннотации @WebMvcTest с Spring Security MockMvc автоматически настраивается с необходимой цепочкой фильтров, необходимой для тестирования нашей конфигурации безопасности.
Поскольку MockMvc настроен для нас, мы можем использовать @WithMockUser для наших тестов без какой-либо дополнительной настройки: весь контекст. Из-за этого тесты контроллера, использующие @WebMvcTest, будут выполняться быстрее, чем при других подходах.
@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());
}
}
6. Тестирование контроллеров с помощью @SpringBootTest
При использовании аннотации @SpringBootTest для тестирования контроллеров с Spring Security необходимо явно настроить цепочку фильтров при настройке MockMvc.
Использование статического метода springSecurity, предоставленного SecurityMockMvcConfigurer, является предпочтительным способом сделать это:
7. Тестирование защищенных методов с помощью @SpringBootTest
@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());
}
}
@SpringBootTest не требует дополнительной настройки для тестирования защищенных методов. Мы можем просто вызывать методы напрямую и использовать @WithMockUser по мере необходимости:
8. Тестирование с помощью @SpringBootTest и TestRestTemplate
@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();
}
}
TestRestTemplate — удобный вариант при написании интеграционных тестов для защищенных конечных точек REST.
Мы можем просто автоматически подключить шаблон и установить учетные данные перед запросом защищенных конечных точек:
TestRestTemplate является гибким и предлагает множество полезных параметров, связанных с безопасностью. Для получения более подробной информации о TestRestTemplate ознакомьтесь с нашей статьей на эту тему.
@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());
}
}
9. Заключение
В этой статье мы рассмотрели несколько способов выполнения интеграционных тестов с поддержкой безопасности.
Мы рассмотрели, как работать с конечными точками mvccontroller и REST, а также с защищенными методами.
Как обычно, весь исходный код примера можно найти на GitHub.
«