Authentifizierung in Web-Anwendungen mit Spring Security 6

Spring Security hilft uns beim Authentifizieren und Autorisieren von Benutzern in Java Web-Anwendungen. Da Spring Security ein mächtiges und komplexes Framework ist, zeige ich in diesem Einstiegsartikel, wie wir die von Spring Boot vorkonfigurierte Spring Security Authentifizierung nutzen und anpassen können.

In diesem Artikel demonstriere ich Spring Security 6 passend zum aktuellen Spring Framework in Version 6. Das Demo Projekt wurde mit der neuen Spring Boot Version 3 erstellt.

Spring Security

Spring Security ist eins mächtiges und flexibel anpassbares Authentifizierungs- und Zugangskontroll-Framework. In der Praxis ist es der Standard zum Absichern von Spring basierten Web-Anwendungen.
Weitere Infos zum Spring Security Projekt findet man hier: https://spring.io/projects/spring-security.

Video zum Blog-Artikel

Spring Security Dependency für das Build-Tool

Mit Spring Boot kann man Spring Security einfach beim Aufsetzen eines neuen Projektes hinzufügen. Das funktioniert so, wie ich es schon in diesem Artikel gezeigt habe: 
microservices-mit-spring-boot-erstellen.html
Allerdings kann man Spring Security auch einfach als Maven Dependency zu existierenden Spring oder Spring Boot Projekten hinzufügen, bei Spring Boot Projekten sieht das so aus:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Da ich zum Bauen der Demo zu diesem Artikel Gradle (https://gradle.org/) verwendet habe, zeige ich hier auch das Gradle Format:

dependencies {
    implementation 'org.springframework.boot:
    spring-boot-starter-security'
...
}

Den kompletten Code zur Spring Security Demo findet ihr in GitHub:

Authentifizierung mit dem Spring Security Standard Benutzer

Nach dem Hinzufügen der Spring Security Dependency ist die Spring Boot Web-Anwendung standardmäßig mit Benutzername und Passwort abgesichert. Das sieht man dann in den Log-Einträgen beim Starten der Anwendung:

...Starting Servlet engine: [Apache Tomcat/10.1.1]...
...Initializing Spring embedded WebApplicationContext
...Root WebApplicationContext: initialization completed in 909 ms
.s.s.UserDetailsServiceAutoConfiguration : 

Using generated security password: 0139...

...Adding welcome page template: index
o.s.s.web.DefaultSecurityFilterChain : Will secure any request with
[org.spring...security...WebAsyncManagerIntegrationFilter@e6cffb4, 
org.spring...security...DefaultLoginPageGeneratingFilter@6507a4d2,
...

In den Logs steht das automatisch, generierte Passwort für den Standard Benutzer mit dem Benutzernamen "user": "Using generated security password: 0139...". Bei jedem Start der Anwendung wird ein neues Passwort generiert.
Außerdem sehen wir noch die Log-Meldung das jeder beliebige Request mit einer Liste von diversen Spring Security Filtern abgesichert wird: "DefaultSecurityFilterChain: Will secure any request with...".

Rufen wir nun im Browser die Web-Application auf (http://localhost:8080), werden wir von den Spring Security Filtern automatisch zur Spring Security Login-Seite weitergeleitet. Der Login-Dialog wird im folgenden Screenshot gezeigt. 


Nach erfolgreichem Login als Benutzer "user" wird der Request wie gewohnt von unseren Spring Controllern verarbeitet, wie das funktioniert ist z.B. hier gezeigt: spring-mvc-thymeleaf.html 

Spring Security Filter Chain

Vereinfacht gesagt, ist Spring Security eine zusätzlich Schicht zwischen dem Client und der Spring Anwendung. Die Spring Security Schicht prüft, ob die Requests des Clients den Security Regeln der Anwendung entsprichen. Security Regeln definieren wir in Form von Filtern. Die Filter bilden eine Kette (Filter Chain). Jeder eingehende Request durchläuft die Filter der Kette entsprechend ihrer Reihenfolge. Dabei prüft jeder Filter, ob dem Request Zugriff auf die Anwendung gewährt wird oder nicht.

Die Logausgabe listet z.B. den Filter DefaultLoginPageGeneratingFilter auf. Wenn wir diese Klasse in unserer IDE öffnen, sehen wir, dass die Superklasse das Filter Interface implementiert. In DefaultLoginPageGeneratingFilter gibt es daher auch die Implementierung der Methode doFilter. In doFilter wird entschieden, ob die Login Seite angezeigt wird oder nicht. Wenn ihr einen Breakpoint in doFilter setzt und die Anwendung im Debug-Modus startet, seht ihr, dass dieser Filter bei jedem Request benutzt wird.

Standard Benutzer konfigurieren

Wenn unsere Web-Anwendungen mit einem Benutzer auskommt, können wir den Standard Benutzer auch per Konfiguration anpassen. Dazu können wir in der application.properties Datei den Benutzernamen und das Passwort selbst definieren:

# Replace Spring Security default username "user" with:
spring.security.user.name=admin-user
# Replace Spring Securtiy randomly generated password with:
spring.security.user.password=geheim123

Nach einem Neustart können wir uns nun als Benutzer "admin-user" mit dem Passwort "geheim123" einloggen.
Spring Boot bietet neben der application.properties Datei noch einige andere Möglichkeiten, um Konfigurationen vorzunehmen. So können wir das Passwort auch an einer anderen Stelle definieren - weitere Infos dazu findet ihr hier: https://docs.spring.io/spring-boot/docs/1.0.1.RELEASE/reference/html/boot-features-external-config.html
 

Mehrere Benutzer und Rollen definieren

Spring Security unterstützt beliebig viele, verschiedene Benutzer. Diese können sich in einer Datenbank, in einem LDAP-Server oder anderen Identity Provider befinden - es werden alle gängigen Lösungen unterstützt. Alternativ kann man Spring Security auch wie benötigt anpassen. Um diesen Einstiegsartikel möglichst einfach zu halten, stelle ich hier nur eine einfache Benutzer-Verwaltung im Speicher vor. Alles was wir dazu brauchen ist eine einfache Spring Bean, die das Interface UserDetailsService implementiert.

@Configuration
public class SecurityConfiguration {
    @Bean
    public UserDetailsService users(@Autowired PasswordEncoder pwEnc)     {
        UserDetails user = User.builder()
                .username("user")
        .password(pwEnc.encode("top"))
        .roles("USER")
        .build();
UserDetails admin = User.builder()
        .username("admin")
        .password(pwEnc.encode("secret"))
        .roles("USER", "ADMIN")
        .build();
return new InMemoryUserDetailsManager(user, admin);
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
    }
}
  • Die  UserDetailsService Bean habe ich per @Bean Annotation in einer Spring Konfigurationsklasse (@Configuration) erstellt. Dieses Vorgehen ist Standard in Spring, siehe dazu auch kernkonzepte-von-spring.html 
  • Konkret wird die UserDetailsService Bean in Form einer Instanz von InMemoryUserDetailsManager bereitgestellt. Dem Konstruktor-Aufruf kann man dabei beliebig viele Benutzer Objekte übergeben (hier im Beispiel sind es nur 2). Alternativen zur Benutzerverwaltung im Speicher mittels InMemoryUserDetailsManager sind zum Beispiel JdbcUserDetailsManager oder CachingUserDetailsService.
  • Die beiden Benutzer wurden hier einfach im Java-Code erstellt. Benutzername und Password sind hier im Beispiel hardcoded - in der Praxis sollte man Credentials auch beim Verwalten der Benutzer im Speicher aus der Spring Konfiguration lesen.
    Die Benutzer (Instanz von UserDetails) werden mittels UserBuilder (User.builder()) erstellt. Das Setzen von username, password und roles (bzw. der Aufruf der gleichnamigen Builder-Methoden) ist dabei Pflicht - alle anderen UserDetails Attribute sind optional. Rollen (roles) werde ich in einem künftigen Blog-Artikel vorstellen, den Demo-Code dazu findet ihr aber jetzt schon im GitHub-Repository zu diesem Artikel.
  • Das Passwort muss encodiert im UserBuilder gesetzt werden. Das mache ich mit der Bean PasswordEncoder, die eine einfache Instanz der BCryptPasswordEncoder Implementierungsklasse ist.
Nach einem Neustart der Spring Boot Applikation könnt ihr den Login mit beiden Benutzern testen, indem ihr die URL http://localhost:8080 jeweils in einem neuen Incognito Browser-Fenster öffnet. Da wir hier noch keine Logout-Funktion implementiert haben, empfehle ich zum Testen den Incognito Modus des Browsers.

Fazit & nächste Schritte

In diesem Blog-Artikel habe ich gezeigt, wie man Spring Security zu einem Spring Boot Projekt hinzufügen kann und was die Standard-Konfiguration von Spring Security mitbringt. In einem kleinen nächsten Schritt haben wir weitere Benutzer für unsere Spring Security abgesicherte Web-Anwendung erstellt.

Möchtet ihr tiefer in das Thema einsteigen? Dann schaut euch noch diese Artikel an:
Hinterlasst mir gerne einen Kommentar mit Fragen, Feedback oder Themen-Wünsche zu Spring Security.

Den kompletten hier vorgestellten Code mit einer Demo-Webanwendung findet ihr hier:

Kommentare

Anonym hat gesagt…
Gibt es die Möglichkeit die Spring Anwendung so zu schützen, dass man nur mit einem Dienst wie SecuRemote darauf zugreifen darf?
Spring Security ermöglicht das Anpassen der Authentifizierung und Zugangskontrolle,
indem man vorhandene Module konfiguriert oder eigene schreibt und sie in die Filter-Chain aufnimmt.
Hier eine Liste vorhandener Module: https://docs.spring.io/spring-security/site/docs/5.4.6/reference/html5/#modules

Alternativ könnte man die Spring Web-Anwendung in ein privates Netzwerk packen
und dann den Zugriff über eine DMZ ermöglichen: https://de.wikipedia.org/wiki/Demilitarisierte_Zone_%28Informatik%29

SecuRemote kenne ich nicht, aber eine dieser beiden Varianten sollte sicher damit funktionieren.

Beliebte Posts aus diesem Blog

OpenID Connect mit Spring Boot 3

CronJobs mit Spring

Kernkonzepte von Spring: Beans und Dependency Injection