Spring Security 5 para aplicações reativas

Spring Security 5 para aplicações reativas

1. Introdução

Neste artigo, vamos explorar novos recursos da estruturaSpring Security 5 para proteger aplicativos reativos. Esta versão está alinhada com o Spring 5 e o Spring Boot 2.

Neste artigo, não entraremos em detalhes sobre os próprios aplicativos reativos, que é um novo recurso do framework Spring 5. Certifique-se de verificar o artigoIntro to Reactor Core para mais detalhes.

2. Configuração do Maven

Usaremos iniciadores Spring Boot para inicializar nosso projeto junto com todas as dependências necessárias.

A configuração básica requer uma declaração pai, um iniciador da Web e dependências do iniciador de segurança. Também precisaremos da estrutura de teste Spring Security:


    org.springframework.boot
    spring-boot-starter-parent
    2.0.0.RELEASE
    



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

Podemos verificar a versão atual do Spring Boot security starterover at Maven Central.

3. Configuração do Projeto

3.1. Inicializando o aplicativo reativo

Não usaremos a configuração@SpringBootApplication padrão, mas em vez disso, configuraremos um servidor web baseado em Netty. Netty is an asynchronous NIO-based framework which is a good foundation for reactive applications.

A anotação@EnableWebFlux ativa a configuração padrão do Spring Web Reactive para o aplicativo:

@ComponentScan(basePackages = {"com.example.security"})
@EnableWebFlux
public class SpringSecurity5Application {

    public static void main(String[] args) {
        try (AnnotationConfigApplicationContext context
         = new AnnotationConfigApplicationContext(
            SpringSecurity5Application.class)) {

            context.getBean(NettyContext.class).onClose().block();
        }
    }

Aqui, criamos um novo contexto de aplicativo e esperamos que o Netty desligue chamando.onClose().block() chain no contexto Netty.

Depois que o Netty for desligado, o contexto será automaticamente fechado usando o blocotry-with-resources.

Também precisaremos criar um servidor HTTP baseado em Netty, um manipulador para as solicitações HTTP e o adaptador entre o servidor e o manipulador:

@Bean
public NettyContext nettyContext(ApplicationContext context) {
    HttpHandler handler = WebHttpHandlerBuilder
      .applicationContext(context).build();
    ReactorHttpHandlerAdapter adapter
      = new ReactorHttpHandlerAdapter(handler);
    HttpServer httpServer = HttpServer.create("localhost", 8080);
    return httpServer.newHandler(adapter).block();
}

3.2. Classe de configuração Spring Security

Para nossa configuração básica do Spring Security, vamos criar uma classe de configuração -SecurityConfig.

Para habilitar o suporte WebFlux no Spring Security 5, precisamos apenas especificar a anotação@EnableWebFluxSecurity:

@EnableWebFluxSecurity
public class SecurityConfig {
    // ...
}

Agora podemos aproveitar a classeServerHttpSecurity para construir nossa configuração de segurança.

This class is a new feature of Spring 5. É semelhante ao construtorHttpSecurity, mas só está habilitado para aplicativos WebFlux.

OServerHttpSecurity já está pré-configurado com alguns padrões lógicos, portanto, poderíamos pular esta configuração completamente. Mas, para começar, forneceremos a seguinte configuração mínima:

@Bean
public SecurityWebFilterChain securitygWebFilterChain(
  ServerHttpSecurity http) {
    return http.authorizeExchange()
      .anyExchange().authenticated()
      .and().build();
}

Além disso, precisaremos de um serviço de detalhes do usuário. O Spring Security nos fornece um conveniente construtor de usuários simulados e uma implementação na memória do serviço de detalhes do usuário:

@Bean
public MapReactiveUserDetailsService userDetailsService() {
    UserDetails user = User
      .withUsername("user")
      .password(passwordEncoder().encode("password"))
      .roles("USER")
      .build();
    return new MapReactiveUserDetailsService(user);
}

Como estamos em terreno reativo, o serviço de detalhes do usuário também deve ser reativo. Se verificarmos a interfaceReactiveUserDetailsService,we’ll see that its findByUsername method actually returns a Mono publisher:

public interface ReactiveUserDetailsService {

    Mono findByUsername(String username);
}

Agora podemos executar nosso aplicativo e observar um formulário de autenticação básica HTTP normal.

4. Formulário de login estilizado

Uma pequena mas impressionante melhoria no Spring Security 5 é um novo formulário de login com estilo que usa a estrutura CSS do Bootstrap 4. As folhas de estilo no formulário de login são vinculadas a CDN, portanto, veremos melhorias apenas quando estivermos conectados à Internet.

Para usar o novo formulário de login, vamos adicionar o método do construtorformLogin() correspondente ao construtorServerHttpSecurity:

public SecurityWebFilterChain securitygWebFilterChain(
  ServerHttpSecurity http) {
    return http.authorizeExchange()
      .anyExchange().authenticated()
      .and().formLogin()
      .and().build();
}

Se agora abrirmos a página principal do aplicativo, veremos que ela parece muito melhor do que a forma padrão com a qual estamos acostumados desde as versões anteriores do Spring Security:

image

 

Observe que este não é um formulário pronto para produção, mas é um bom bootstrap de nosso aplicativo.

Se agora fizermos login e formos para o URLhttp://localhost:8080/logout, veremos o formulário de confirmação de logout, que também é estilizado.

5. Segurança do controlador reativo

Para ver algo por trás do formulário de autenticação, vamos implementar um controlador reativo simples que cumprimenta o usuário:

@RestController
public class GreetController {

    @GetMapping("/")
    public Mono greet(Mono principal) {
        return principal
          .map(Principal::getName)
          .map(name -> String.format("Hello, %s", name));
    }

}

Depois de fazer login, veremos a saudação. Vamos adicionar outro manipulador reativo que pode ser acessado apenas pelo administrador:

@GetMapping("/admin")
public Mono greetAdmin(Mono principal) {
    return principal
      .map(Principal::getName)
      .map(name -> String.format("Admin access: %s", name));
}

Agora vamos criar um segundo usuário com a funçãoADMIN: em nosso serviço de detalhes do usuário:

UserDetails admin = User.withDefaultPasswordEncoder()
  .username("admin")
  .password("password")
  .roles("ADMIN")
  .build();

Agora podemos adicionar uma regra de correspondência para a URL admin que requer que o usuário tenha autoridadeROLE_ADMIN.

Chamada em cadeia deNote that we have to put matchers before the .anyExchange(). Esta chamada se aplica a todos os outros URLs que ainda não foram cobertos por outros correspondentes:

return http.authorizeExchange()
  .pathMatchers("/admin").hasAuthority("ROLE_ADMIN")
  .anyExchange().authenticated()
  .and().formLogin()
  .and().build();

Se agora fizermos login comuser ouadmin, veremos que ambos observam a saudação inicial, já que a tornamos acessível para todos os usuários autenticados.

But only the admin user can go to the http://localhost:8080/admin URL and see her greeting.

6. Segurança do método reativo

Vimos como podemos proteger os URLs, mas e os métodos?

Para habilitar a segurança baseada em método para métodos reativos, precisamos apenas adicionar a anotação@EnableReactiveMethodSecurity à nossa classeSecurityConfig:

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {
    // ...
}

Agora vamos criar um serviço de saudação reativa com o seguinte conteúdo:

@Service
public class GreetService {

    public Mono greet() {
        return Mono.just("Hello from service!");
    }
}

Podemos injetá-lo no controlador, ir parahttp://localhost:8080/greetServicee ver se ele realmente funciona:

@RestController
public class GreetController {

    private GreetService greetService

    @GetMapping("/greetService")
    public Mono greetService() {
        return greetService.greet();
    }

    // standard constructors...
}

Mas se agora adicionarmos a anotação@PreAuthorize no método de serviço com a funçãoADMIN, então o URL do serviço de saudação não ficará acessível para um usuário comum:

@Service
public class GreetService {

@PreAuthorize("hasRole('ADMIN')")
public Mono greet() {
    // ...
}

7. Zombando de usuários em testes

Vamos verificar como é fácil testar nosso aplicativo Spring reativo.

Primeiro, vamos criar um teste com um contexto de aplicativo injetado:

@ContextConfiguration(classes = SpringSecurity5Application.class)
public class SecurityTest {

    @Autowired
    ApplicationContext context;

    // ...
}

Agora vamos configurar um cliente de teste da web reativo simples, que é um recurso da estrutura de teste do Spring 5:

@Before
public void setup() {
    this.rest = WebTestClient
      .bindToApplicationContext(this.context)
      .configureClient()
      .build();
}

Isso nos permite verificar rapidamente se o usuário não autorizado é redirecionado da página principal do nosso aplicativo para a página de login:

@Test
public void whenNoCredentials_thenRedirectToLogin() {
    this.rest.get()
      .uri("/")
      .exchange()
      .expectStatus().is3xxRedirection();
}

Se agora adicionarmos a anotação@MockWithUser a um método de teste, podemos fornecer um usuário autenticado para este método.

O login e a senha desse usuário seriamuserepassword respectivamente, e a função éUSER. Isso, é claro, pode ser configurado com os parâmetros de anotação@MockWithUser.

Agora podemos verificar se o usuário autorizado vê a saudação:

@Test
@WithMockUser
public void whenHasCredentials_thenSeesGreeting() {
    this.rest.get()
      .uri("/")
      .exchange()
      .expectStatus().isOk()
      .expectBody(String.class).isEqualTo("Hello, user");
}

A anotação@WithMockUser está disponível desde Spring Security 4. No entanto, no Spring Security 5, ele também foi atualizado para abranger pontos de extremidade e métodos reativos.

8. Conclusão

Neste tutorial, descobrimos novos recursos do próximo lançamento do Spring Security 5, especialmente na área de programação reativa.

Como sempre, o código-fonte do artigo está disponívelover on GitHub.