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:
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.