Spring Security 5 per applicazioni reattive

1. Introduzione

In questo articolo, esploreremo le nuove funzionalità del framework Spring Security 5 per proteggere le applicazioni reattive. Questa versione è allineata con Spring 5 e Spring Boot 2.

In questo articolo, non entreremo nei dettagli sulle applicazioni reattive stesse, che è una nuova funzionalità del framework Spring 5. Assicurati di controllare l'articolo Intro to Reactor Core per maggiori dettagli.

2. Installazione di Maven

Useremo gli starter Spring Boot per avviare il nostro progetto insieme a tutte le dipendenze richieste.

La configurazione di base richiede una dichiarazione genitore, un web starter e dipendenze di security starter. Avremo anche bisogno del framework di test Spring Security:

 org.springframework.boot spring-boot-starter-parent 2.2.6.RELEASE     org.springframework.boot spring-boot-starter-webflux   org.springframework.boot spring-boot-starter-security   org.springframework.security spring-security-test test  

Possiamo controllare la versione corrente dello starter di sicurezza Spring Boot su Maven Central.

3. Configurazione del progetto

3.1. Avvio dell'applicazione reattiva

Non useremo la configurazione standard @SpringBootApplication , ma configureremo invece un server web basato su Netty. Netty è un framework asincrono basato su NIO che rappresenta una buona base per applicazioni reattive.

L' annotazione @EnableWebFlux abilita la configurazione standard Spring Web Reactive per l'applicazione:

@ComponentScan(basePackages = {"com.baeldung.security"}) @EnableWebFlux public class SpringSecurity5Application { public static void main(String[] args) { try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( SpringSecurity5Application.class)) { context.getBean(NettyContext.class).onClose().block(); } }

Qui, creiamo un nuovo contesto dell'applicazione e aspettiamo che Netty si chiuda chiamando la catena .onClose (). Block () sul contesto Netty.

Dopo che Netty è stato chiuso, il contesto verrà chiuso automaticamente utilizzando il blocco try-with-resources .

Dovremo anche creare un server HTTP basato su Netty, un gestore per le richieste HTTP e l'adattatore tra il server e il gestore:

@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 di configurazione Spring Security

Per la nostra configurazione di base Spring Security, creeremo una classe di configurazione - SecurityConfig .

Per abilitare il supporto WebFlux in Spring Security 5, dobbiamo solo specificare l' annotazione @EnableWebFluxSecurity :

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

Ora possiamo sfruttare la classe ServerHttpSecurity per creare la nostra configurazione di sicurezza.

Questa classe è una nuova funzionalità di Spring 5. È simile al builder HttpSecurity , ma è abilitata solo per le applicazioni WebFlux.

Il ServerHttpSecurity è già preconfigurato con alcuni valori di default sane, così abbiamo potuto ignorare completamente questa configurazione. Ma per i principianti, forniremo la seguente configurazione minima:

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

Inoltre, avremo bisogno di un servizio di dettagli utente. Spring Security ci fornisce un pratico generatore di falsi utenti e un'implementazione in memoria del servizio dettagli utente:

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

Dato che siamo in terra reattiva, anche il servizio dettagli utente dovrebbe essere reattivo. Se controlliamo l' interfaccia ReactiveUserDetailsService , vedremo che il suo metodo findByUsername restituisce effettivamente un editore Mono :

public interface ReactiveUserDetailsService { Mono findByUsername(String username); }

Ora possiamo eseguire la nostra applicazione e osservare un normale modulo di autenticazione di base HTTP.

4. Modulo di accesso con stile

Un piccolo ma sorprendente miglioramento in Spring Security 5 è un nuovo modulo di accesso in stile che utilizza il framework CSS Bootstrap 4. I fogli di stile nel modulo di accesso si collegano a CDN, quindi vedremo il miglioramento solo quando ci si connette a Internet.

Per utilizzare il nuovo form di login, aggiungiamo il corrispondente metodo builder formLogin () al builder ServerHttpSecurity :

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

Se ora apriamo la pagina principale dell'applicazione, vedremo che ha un aspetto molto migliore rispetto al modulo predefinito a cui siamo abituati dalle versioni precedenti di Spring Security:

Nota che questo non è un modulo pronto per la produzione, ma è un buon bootstrap della nostra applicazione.

Se ora effettuiamo il login e poi andiamo all'URL // localhost: 8080 / logout, vedremo il modulo di conferma della disconnessione, anch'esso in stile.

5. Reactive Controller Security

To see something behind the authentication form, let's implement a simple reactive controller that greets the user:

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

After logging in, we'll see the greeting. Let's add another reactive handler that would be accessible by admin only:

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

Now let's create a second user with the role ADMIN: in our user details service:

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

We can now add a matcher rule for the admin URL that requires the user to have the ROLE_ADMIN authority.

Note that we have to put matchers before the .anyExchange() chain call. This call applies to all other URLs which were not yet covered by other matchers:

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

If we now log in with user or admin, we'll see that they both observe initial greeting, as we've made it accessible for all authenticated users.

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

6. Reactive Method Security

We've seen how we can secure the URLs, but what about methods?

To enable method-based security for reactive methods, we only need to add the @EnableReactiveMethodSecurity annotation to our SecurityConfig class:

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

Now let's create a reactive greeting service with the following content:

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

We can inject it into the controller, go to //localhost:8080/greetService and see that it actually works:

@RestController public class GreetController { private GreetService greetService @GetMapping("/greetService") public Mono greetService() { return greetService.greet(); } // standard constructors... }

But if we now add the @PreAuthorize annotation on the service method with the ADMIN role, then the greet service URL won't be accessible to a regular user:

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

7. Mocking Users in Tests

Let's check out how easy it is to test our reactive Spring application.

First, we'll create a test with an injected application context:

@ContextConfiguration(classes = SpringSecurity5Application.class) public class SecurityTest { @Autowired ApplicationContext context; // ... }

Now we'll set up a simple reactive web test client, which is a feature of the Spring 5 test framework:

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

This allows us to quickly check that the unauthorized user is redirected from the main page of our application to the login page:

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

If we now add the @MockWithUser annotation to a test method, we can provide an authenticated user for this method.

Il login e la password di questo utente sarebbero rispettivamente utente e password e il ruolo è USER . Questo, ovviamente, può essere configurato con i parametri di annotazione @MockWithUser .

Ora possiamo verificare che l'utente autorizzato veda il saluto:

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

L' annotazione @WithMockUser è disponibile a partire da Spring Security 4. Tuttavia, in Spring Security 5 è stata aggiornata anche per coprire endpoint e metodi reattivi.

8. Conclusione

In questo tutorial, abbiamo scoperto le nuove funzionalità della prossima versione di Spring Security 5, soprattutto nell'arena della programmazione reattiva.

Come sempre, il codice sorgente dell'articolo è disponibile su GitHub.