Spring Security para testes de integração do Spring Boot

Spring Security para testes de integração do Spring Boot

1. Introdução

A capacidade de executar testes de integração sem a necessidade de um ambiente de integração independente é um recurso valioso para qualquer pilha de software. A integração perfeita do Spring Boot com o Spring Security simplifica o teste de componentes que interagem com uma camada de segurança.

Neste tutorial rápido, exploraremos o uso de@MockMvcTeste@SpringBootTest para executar testes de integração habilitados para segurança.

2. Dependências

Vamos primeiro trazer as dependências de que precisaremos para nosso exemplo:


    org.springframework.boot
    spring-boot-starter-security


    org.springframework.boot
    spring-boot-starter-web


    org.springframework.boot
    spring-boot-starter-test
    test


    org.springframework.security
    spring-security-test
    test

Os startersspring-boot-starter-webspring-boot-starter-security,espring-boot-starter-test  nos fornecem acesso aos utilitários de teste Spring MVC, Spring Security e Spring Boot.

Além disso, vamos trazerspring-security-test para obter acesso à anotação@WithMockUser que usaremos.

3. Configuração de Segurança da Web

Nossa configuração de segurança na web será direta. Apenas usuários autenticados poderão acessar caminhos que correspondam a/private/. Os caminhos que correspondem a/public/ estarão disponíveis para qualquer usuário:

@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. Configuração de Segurança do Método

Além da segurança baseada em caminho de URL que definimos em nossoWebSecurityConfigurer,, podemos configurar a segurança baseada em método fornecendo um arquivo de configuração adicional:

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

Esta configuração permite suporte para anotações pré / pós do Spring Security. Outros atributos também estão disponíveis se for necessário suporte adicional. Para obter mais informações sobre Spring Method Security, dê uma olhada em nossoarticle on the topic.

5. Testando controladores com@WebMvcTest

Ao usar a abordagem de anotação@WebMvcTest com Spring Security,MockMvc is automatically configured with the necessary filter chain required para testar nossa configuração de segurança.

ComoMockMvc está configurado para nós, podemos usar@WithMockUser para nossos testes sem nenhuma configuração adicional:

@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());
    }
}

Observe que usar@WebMvcTest dirá ao Spring Boot para instanciar apenas a camada da web e não todo o contexto. Por causa disso,controller tests that use @WebMvcTest will run faster than with other approaches.

6. Testando controladores com@SpringBootTest

Ao usar a anotação@SpringBootTest para testar controladores com Spring Security,it’s necessary to explicitly configure the filter chain when setting up MockMvc.

Usar o métodospringSecurity estático fornecido porSecurityMockMvcConfigurer é a maneira preferida de fazer isso:

@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. Testando métodos seguros com@SpringBootTest

@SpringBootTest não requer nenhuma configuração adicional para testar métodos seguros. Podemossimply call the methods directly and use @WithMockUser as needed:

@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. Testando com@SpringBootTest eTestRestTemplate

TestRestTemplate é uma opção conveniente ao escrever testes de integração para pontos de extremidade REST protegidos.

Podemossimply autowire a template and set credentials before requesting secured endpoints:

@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 result = template.withBasicAuth("spring", "secret")
          .getForEntity("/private/hello", String.class);
        assertEquals(HttpStatus.OK, result.getStatusCode());
    }
}

TestRestTemplate é flexível e oferece muitas opções úteis relacionadas à segurança. Para obter mais detalhes sobreTestRestTemplate, verifique nossoarticle on the topic.

9. Conclusão

Neste artigo, vimos várias maneiras de executar testes de integração habilitados para segurança.

Vimos como trabalhar com os pontos de extremidade mvccontroller e REST e também com métodos seguros.

Como de costume, todo o código-fonte para o exemplo aqui pode serfound over on GitHub.