Spring Security 5 für reaktive Anwendungen

Federsicherheit 5 für reaktive Anwendungen

1. Einführung

In diesem Artikel werden neue Funktionen desSpring Security 5-Frameworks zum Sichern reaktiver Anwendungen untersucht. Diese Version ist auf Spring 5 und Spring Boot 2 abgestimmt.

In diesem Artikel werden wir nicht auf die reaktiven Anwendungen selbst eingehen. Dies ist eine neue Funktion des Spring 5-Frameworks. Weitere Informationen finden Sie im ArtikelIntro to Reactor Core.

2. Maven Setup

Wir werden Spring Boot-Starter verwenden, um unser Projekt zusammen mit allen erforderlichen Abhängigkeiten zu booten.

Die Grundkonfiguration erfordert eine übergeordnete Deklaration, einen Webstarter und Sicherheitsstarter-Abhängigkeiten. Wir benötigen außerdem das Spring Security-Testframework:


    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
    

Wir können uns die aktuelle Version von Spring Boot Security Starterover at Maven Central ansehen.

3. Projektaufbau

3.1. Bootstrapping der reaktiven Anwendung

Wir verwenden nicht die Standardkonfiguration von@SpringBootApplication, sondern konfigurieren stattdessen einen Netty-basierten Webserver. Netty is an asynchronous NIO-based framework which is a good foundation for reactive applications.

Die Annotation@EnableWebFlux aktiviert die Standardkonfiguration von Spring Web Reactive für die Anwendung:

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

Hier erstellen wir einen neuen Anwendungskontext und warten, bis Netty heruntergefahren ist, indem wir die Kette von.onClose().block()im Netty-Kontext aufrufen.

Nach dem Herunterfahren von Netty wird der Kontext automatisch mit dem Blocktry-with-resourcesgeschlossen.

Außerdem müssen wir einen Netty-basierten HTTP-Server, einen Handler für die HTTP-Anforderungen und den Adapter zwischen dem Server und dem Handler erstellen:

@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. Spring Security-Konfigurationsklasse

Für unsere grundlegende Spring Security-Konfiguration erstellen wir eine Konfigurationsklasse -SecurityConfig.

Um die WebFlux-Unterstützung in Spring Security 5 zu aktivieren, müssen Sie nur die Annotation@EnableWebFluxSecurityangeben:

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

Jetzt können wir die KlasseServerHttpSecurity nutzen, um unsere Sicherheitskonfiguration zu erstellen.

This class is a new feature of Spring 5. Es ähnelt dem Builder vonHttpSecurity, ist jedoch nur für WebFlux-Anwendungen aktiviert.

ServerHttpSecurity ist bereits mit einigen vernünftigen Standardeinstellungen vorkonfiguriert, sodass wir diese Konfiguration vollständig überspringen können. Für den Anfang bieten wir jedoch die folgende minimale Konfiguration:

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

Außerdem benötigen wir einen Benutzerdetailservice. Spring Security bietet uns einen praktischen Mock-User-Builder und eine In-Memory-Implementierung des User-Details-Service:

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

Da wir uns in einem reaktiven Land befinden, sollte der Benutzerdetailservice auch reaktiv sein. Wenn wir uns dieReactiveUserDetailsService-Schnittstelle ansehen,we’ll see that its findByUsername method actually returns a Mono publisher:

public interface ReactiveUserDetailsService {

    Mono findByUsername(String username);
}

Jetzt können wir unsere Anwendung ausführen und ein reguläres HTTP-Basisauthentifizierungsformular einhalten.

4. Gestyltes Anmeldeformular

Eine kleine, aber bemerkenswerte Verbesserung in Spring Security 5 ist ein neues Anmeldeformular, das das Bootstrap 4-CSS-Framework verwendet. Die Stylesheets im Anmeldeformular sind mit CDN verknüpft, sodass wir die Verbesserung nur sehen, wenn Sie mit dem Internet verbunden sind.

Um das neue Anmeldeformular zu verwenden, fügen wir dem Builder vonServerHttpSecuritydie entsprechende Builder-MethodeformLogin()hinzu:

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

Wenn wir jetzt die Hauptseite der Anwendung öffnen, werden wir feststellen, dass sie viel besser aussieht als das Standardformular, das wir seit früheren Versionen von Spring Security verwendet haben:

image

 

Beachten Sie, dass dies kein produktionsfähiges Formular ist, aber ein guter Bootstrap unserer Anwendung.

Wenn wir uns jetzt anmelden und dann zur URL vonhttp://localhost:8080/logoutwechseln, wird das Bestätigungsformular für die Abmeldung angezeigt, das ebenfalls gestaltet ist.

5. Reaktive Controller-Sicherheit

Um etwas hinter dem Authentifizierungsformular zu sehen, implementieren wir einen einfachen reaktiven Controller, der den Benutzer begrüßt:

@RestController
public class GreetController {

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

}

Nach dem Anmelden wird die Begrüßung angezeigt. Fügen wir einen weiteren reaktiven Handler hinzu, auf den nur der Administrator zugreifen kann:

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

Erstellen wir nun einen zweiten Benutzer mit der RolleADMIN: in unserem Benutzerdetailservice:

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

Wir können jetzt eine Matcher-Regel für die Administrator-URL hinzufügen, für die der Benutzer die BerechtigungROLE_ADMINhaben muss.

Note that we have to put matchers before the .anyExchange() Kettenaufruf. Dieser Aufruf gilt für alle anderen URLs, die noch nicht von anderen Matchern abgedeckt wurden:

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

Wenn wir uns jetzt mituser oderadmin anmelden, werden wir feststellen, dass beide die erste Begrüßung beobachten, da wir sie für alle authentifizierten Benutzer zugänglich gemacht haben.

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

6. Sicherheit reaktiver Methoden

Wir haben gesehen, wie wir die URLs sichern können, aber was ist mit Methoden?

Um die methodenbasierte Sicherheit für reaktive Methoden zu aktivieren, müssen wir nur die Annotation@EnableReactiveMethodSecurityzu unserer KlasseSecurityConfighinzufügen:

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

Erstellen wir nun einen reaktiven Begrüßungsdienst mit folgendem Inhalt:

@Service
public class GreetService {

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

Wir können es in den Controller einspeisen, zuhttp://localhost:8080/greetService gehen und sehen, dass es tatsächlich funktioniert:

@RestController
public class GreetController {

    private GreetService greetService

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

    // standard constructors...
}

Wenn wir jetzt die Annotation@PreAuthorizezur Servicemethode mit der RolleADMINhinzufügen, ist die URL des Begrüßungsdienstes für einen normalen Benutzer nicht zugänglich:

@Service
public class GreetService {

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

7. Benutzer in Tests verspotten

Schauen wir uns an, wie einfach es ist, unsere reaktive Spring-Anwendung zu testen.

Zunächst erstellen wir einen Test mit einem injizierten Anwendungskontext:

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

    @Autowired
    ApplicationContext context;

    // ...
}

Jetzt richten wir einen einfachen reaktiven Webtest-Client ein, der eine Funktion des Spring 5-Testframeworks ist:

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

Auf diese Weise können wir schnell überprüfen, ob der nicht autorisierte Benutzer von der Hauptseite unserer Anwendung auf die Anmeldeseite umgeleitet wurde:

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

Wenn wir jetzt die Annotation@MockWithUserzu einer Testmethode hinzufügen, können wir einen authentifizierten Benutzer für diese Methode bereitstellen.

Das Login und Passwort dieses Benutzers wäreuser bzw.password, und die Rolle istUSER. Dies kann natürlich alles mit den Annotationsparametern von@MockWithUserkonfiguriert werden.

Jetzt können wir überprüfen, ob der berechtigte Benutzer die Begrüßung sieht:

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

Die Annotation@WithMockUser ist seit Spring Security 4 verfügbar. In Spring Security 5 wurde es jedoch auch aktualisiert, um reaktive Endpunkte und Methoden abzudecken.

8. Fazit

In diesem Tutorial haben wir neue Funktionen der kommenden Version von Spring Security 5 entdeckt, insbesondere im Bereich der reaktiven Programmierung.

Wie immer ist der Quellcode für den Artikelover on GitHub verfügbar.