Spring Security 5 pour applications réactives

Spring Security 5 pour applications réactives

1. introduction

Dans cet article, nous allons explorer les nouvelles fonctionnalités du frameworkSpring Security 5 pour sécuriser les applications réactives. Cette version est alignée sur Spring 5 et Spring Boot 2.

Dans cet article, nous n'entrerons pas dans les détails sur les applications réactives elles-mêmes, qui est une nouvelle fonctionnalité du framework Spring 5. N'oubliez pas de consulter l'articleIntro to Reactor Core pour plus de détails.

2. Maven Setup

Nous utiliserons les démarreurs Spring Boot pour démarrer notre projet avec toutes les dépendances requises.

La configuration de base nécessite une déclaration parente, un démarreur Web et des dépendances de démarreur de sécurité. Nous aurons également besoin du framework de test 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
    

Nous pouvons consulter la version actuelle du démarreur de sécurité Spring Bootover at Maven Central.

3. Configuration du projet

3.1. Amorcer l'application réactive

Nous n'utiliserons pas la configuration standard de@SpringBootApplication, mais plutôt un serveur Web basé sur Netty. Netty is an asynchronous NIO-based framework which is a good foundation for reactive applications.

L'annotation@EnableWebFlux active la configuration standard Spring Web Reactive pour l'application:

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

Ici, nous créons un nouveau contexte d'application et attendons que Netty s'arrête en appelant la chaîne.onClose().block() sur le contexte Netty.

Une fois Netty arrêté, le contexte sera automatiquement fermé à l'aide du bloctry-with-resources.

Nous devrons également créer un serveur HTTP basé sur Netty, un gestionnaire pour les requêtes HTTP et l'adaptateur entre le serveur et le gestionnaire:

@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 configuration de sécurité Spring

Pour notre configuration de base de Spring Security, nous allons créer une classe de configuration -SecurityConfig.

Pour activer la prise en charge de WebFlux dans Spring Security 5, il suffit de spécifier l'annotation@EnableWebFluxSecurity:

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

Nous pouvons maintenant profiter de la classeServerHttpSecurity pour construire notre configuration de sécurité.

This class is a new feature of Spring 5. Il est similaire au générateurHttpSecurity, mais il n'est activé que pour les applications WebFlux.

LeServerHttpSecurity est déjà préconfiguré avec des valeurs par défaut saines, nous pourrions donc ignorer complètement cette configuration. Mais pour commencer, nous fournirons la configuration minimale suivante:

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

De plus, nous aurons besoin d'un service d'informations sur l'utilisateur. Spring Security nous fournit un constructeur fictif d’utilisateurs et une implémentation en mémoire du service de détails d’utilisateur:

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

Étant donné que nous sommes dans un pays réactif, le service d'informations sur l'utilisateur doit également être réactif. Si nous vérifions l'interfaceReactiveUserDetailsService,we’ll see that its findByUsername method actually returns a Mono publisher:

public interface ReactiveUserDetailsService {

    Mono findByUsername(String username);
}

Nous pouvons maintenant exécuter notre application et observer un formulaire d'authentification de base HTTP standard.

4. Formulaire de connexion stylé

Spring Security 5 constitue une amélioration légère mais frappante: un nouveau formulaire de connexion stylé utilisant le framework CSS Bootstrap 4. Les feuilles de style dans le formulaire de connexion sont liées à CDN, nous ne verrons donc l'amélioration que lorsque nous nous connectons à Internet.

Pour utiliser le nouveau formulaire de connexion, ajoutons la méthode de générateurformLogin() correspondante au générateurServerHttpSecurity:

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

Si nous ouvrons maintenant la page principale de l'application, nous verrons qu'elle est bien meilleure que le formulaire par défaut auquel nous sommes habitués depuis les versions précédentes de Spring Security:

image

 

Notez que ce formulaire n’est pas prêt pour la production, mais qu’il s’agit d’un bon démarrage de notre application.

Si nous nous connectons maintenant et que nous accédons à l'URL dehttp://localhost:8080/logout, nous verrons le formulaire de confirmation de déconnexion, qui est également stylé.

5. Sécurité du contrôleur réactif

Pour voir quelque chose derrière le formulaire d'authentification, implémentons un contrôleur réactif simple qui salue l'utilisateur:

@RestController
public class GreetController {

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

}

Une fois connecté, nous verrons le message d'accueil. Ajoutons un autre gestionnaire réactif qui ne serait accessible que par l'administrateur:

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

Créons maintenant un deuxième utilisateur avec le rôleADMIN: dans notre service de détails utilisateur:

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

Nous pouvons maintenant ajouter une règle de mise en correspondance pour l'URL d'administration qui exige que l'utilisateur ait l'autoritéROLE_ADMIN.

Appel en chaîneNote that we have to put matchers before the .anyExchange(). Cet appel s’applique à toutes les autres URL qui n’étaient pas encore couvertes par d’autres correspondants:

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

Si nous nous connectons maintenant avecuser ouadmin, nous verrons qu'ils observent tous les deux le message d'accueil initial, car nous l'avons rendu accessible à tous les utilisateurs authentifiés.

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

6. Sécurité des méthodes réactives

Nous avons vu comment nous pouvons sécuriser les URL, mais qu'en est-il des méthodes?

Pour activer la sécurité basée sur les méthodes pour les méthodes réactives, il suffit d'ajouter l'annotation@EnableReactiveMethodSecurity à notre classeSecurityConfig:

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

Créons maintenant un service d'accueil réactif avec le contenu suivant:

@Service
public class GreetService {

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

Nous pouvons l'injecter dans le contrôleur, aller àhttp://localhost:8080/greetService et voir que cela fonctionne réellement:

@RestController
public class GreetController {

    private GreetService greetService

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

    // standard constructors...
}

Mais si nous ajoutons maintenant l'annotation@PreAuthorize sur la méthode de service avec le rôleADMIN, alors l'URL du service de bienvenue ne sera pas accessible à un utilisateur normal:

@Service
public class GreetService {

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

7. Se moquer des utilisateurs dans les tests

Voyons à quel point il est facile de tester notre application Spring réactive.

Tout d'abord, nous allons créer un test avec un contexte d'application injecté:

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

    @Autowired
    ApplicationContext context;

    // ...
}

Nous allons maintenant configurer un simple client de test Web réactif, qui est une fonctionnalité du framework de test Spring 5:

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

Cela nous permet de vérifier rapidement que l'utilisateur non autorisé est redirigé de la page principale de notre application vers la page de connexion:

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

Si nous ajoutons maintenant l'annotation@MockWithUser à une méthode de test, nous pouvons fournir un utilisateur authentifié pour cette méthode.

Le login et le mot de passe de cet utilisateur sont respectivementuser etpassword, et le rôle estUSER. Tout ceci, bien sûr, peut être configuré avec les paramètres d'annotation@MockWithUser.

Maintenant, nous pouvons vérifier que l'utilisateur autorisé voit le message d'accueil:

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

L'annotation@WithMockUser est disponible depuis Spring Security 4. Cependant, dans Spring Security 5, il a également été mis à jour pour couvrir les points de terminaison et les méthodes réactifs.

8. Conclusion

Dans ce didacticiel, nous avons découvert les nouvelles fonctionnalités de la prochaine version de Spring Security 5, en particulier dans le domaine de la programmation réactive.

Comme toujours, le code source de l'article est disponibleover on GitHub.