19. Sichern des Zugriffs auf einen Webdienst mit Spring Security
19.1. Die Rolle von Spring Security in einer Webanwendung
Betrachten wir Spring Security im Kontext der Entwicklung einer Webanwendung. Meistens basiert diese auf einer mehrschichtigen Architektur wie der folgenden:
![]() |
- Die [Spring Security]-Schicht gewährt nur autorisierten Benutzern Zugriff auf die [Web]-Schicht.
19.2. Ein Tutorial zu Spring Security
Wir werden erneut ein Spring-Handbuch importieren, indem wir die folgenden Schritte 1 bis 3 ausführen:
![]() |
![]() |
Das Projekt umfasst folgende Elemente:
- Im Ordner [templates] finden Sie die HTML-Seiten des Projekts;
- [Application]: ist die ausführbare Klasse des Projekts;
- [MvcConfig]: ist die Spring-MVC-Konfigurationsklasse;
- [WebSecurityConfig]: ist die Spring Security-Konfigurationsklasse;
19.2.1. Maven-Konfiguration
Projekt [3] ist ein Maven-Projekt. Werfen wir einen Blick auf die Datei [pom.xml], um die Abhängigkeiten zu sehen:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-securing-web</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- tag::security[] -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- end::security[] -->
</dependencies>
<properties>
<start-class>hello.Application</start-class>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- Zeilen 10–14: Das Projekt ist ein Spring Boot-Projekt;
- Zeilen 17–20: Abhängigkeit vom [Thymeleaf]-Framework;
- Zeilen 22–25: Abhängigkeit vom Spring Security-Framework;
19.2.2. Thymeleaf-Ansichten
![]() |
Die Ansicht [home.html] sieht wie folgt aus:
![]() |
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<h1>Welcome!</h1>
<p>
Click <a th:href="@{/hello}">here</a> to see a greeting.
</p>
</body>
</html>
- Zeile 12: Das Attribut [th:href="@{/hello}"] generiert das [href]-Attribut des <a>-Tags. Der Wert [@{/hello}] generiert den Pfad [<context>/hello], wobei [context] der Kontext der Webanwendung ist;
Der generierte HTML-Code lautet wie folgt:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<h1>Welcome!</h1>
<p>
Click
<a href="/hello">here</a>
to see a greeting.
</p>
</body>
</html>
Die Ansicht [hello.html] sieht wie folgt aus:
![]() |
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out" />
</form>
</body>
</html>
- Zeile 9: Das Attribut [th:inline="text"] generiert den Text des Tags <h1>. Dieser Text enthält einen $-Ausdruck, der ausgewertet werden muss. Das Element [[${#httpServletRequest.remoteUser}]] ist der Wert des Attributs [RemoteUser] der aktuellen HTTP-Anfrage. Dies ist der Name des angemeldeten Benutzers;
- Zeile 10: ein HTML-Formular. Das Attribut [th:action="@{/logout}"] generiert das Attribut [action] des Tags [form]. Der Wert [@{/logout}] generiert den Pfad [<context>/logout], wobei [context] der Kontext der Webanwendung ist;
Der generierte HTML-Code lautet wie folgt:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<h1>Hello user!</h1>
<form method="post" action="/logout">
<input type="submit" value="Sign Out" />
<input type="hidden" name="_csrf" value="b152e5b9-d1a4-4492-b89d-b733fe521c91" />
</form>
</body>
</html>
- Zeile 8: die Übersetzung von „Hallo [[${#httpServletRequest.remoteUser}]]!“;
- Zeile 9: die Übersetzung von @{/logout};
- Zeile 11: ein verstecktes Feld mit dem Namen (Attribut „name“) _csrf;
Die endgültige Ansicht [login.html] sieht wie folgt aus:
![]() |
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<div th:if="${param.error}">Invalid username and password.</div>
<div th:if="${param.logout}">You have been logged out.</div>
<form th:action="@{/login}" method="post">
<div>
<label> User Name : <input type="text" name="username" />
</label>
</div>
<div>
<label> Password: <input type="password" name="password" />
</label>
</div>
<div>
<input type="submit" value="Sign In" />
</div>
</form>
</body>
</html>
- Zeile 9: Das Attribut [th:if="${param.error}"] stellt sicher, dass das <div>-Tag nur generiert wird, wenn die URL, die die Anmeldeseite anzeigt, den Parameter [error] enthält (http://context/login?error);
- Zeile 10: Das Attribut [th:if="${param.logout}"] stellt sicher, dass das <div>-Tag nur generiert wird, wenn die URL, die die Anmeldeseite anzeigt, den Parameter [logout] enthält (http://context/login?logout);
- Zeilen 11–23: ein HTML-Formular;
- Zeile 11: Das Formular wird an die URL [<context>/login] gesendet, wobei <context> der Kontext der Webanwendung ist;
- Zeile 13: ein Eingabefeld mit dem Namen [username];
- Zeile 17: ein Eingabefeld mit dem Namen [password];
Der generierte HTML-Code lautet wie folgt:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div>
You have been logged out.
</div>
<form method="post" action="/login">
<div>
<label>
User Name :
<input type="text" name="username" />
</label>
</div>
<div>
<label>
Password:
<input type="password" name="password" />
</label>
</div>
<div>
<input type="submit" value="Sign In" />
</div>
<input type="hidden" name="_csrf" value="ef809b0a-88b4-4db9-bc53-342216b77632" />
</form>
</body>
</html>
Beachten Sie in Zeile 28, dass Thymeleaf ein verstecktes Feld namens [_csrf] hinzugefügt hat.
19.2.3. Spring MVC-Konfiguration
![]() |
Die Klasse [MvcConfig] konfiguriert das Spring MVC-Framework:
package hello;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("home");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
}
}
- Zeile 7: Die Annotation [@Configuration] macht die Klasse [MvcConfig] zu einer Konfigurationsklasse;
- Zeile 8: Die Klasse [MvcConfig] erweitert die Klasse [WebMvcConfigurerAdapter], um bestimmte Methoden zu überschreiben;
- Zeile 10: Neudefinition einer Methode aus der übergeordneten Klasse;
- Zeilen 11–16: Die Methode [addViewControllers] ermöglicht die Zuordnung von URLs zu HTML-Ansichten. Dort werden folgende Zuordnungen vorgenommen:
Ansicht | |
/templates/home.html | |
/templates/hello.html | |
/templates/login.html |
Die Endung [html] und der Ordner [templates] sind die von Thymeleaf verwendeten Standardwerte. Sie können über die Konfiguration geändert werden. Der Ordner [templates] muss sich im Stammverzeichnis des Klassenpfads des Projekts befinden:
![]() |
In [1] oben sind die Ordner [java] und [resources] beide Quellordner. Das bedeutet, dass sich ihr Inhalt im Stammverzeichnis des Klassenpfads des Projekts befindet. Daher befinden sich in [2] die Ordner [hello] und [templates] im Stammverzeichnis des Klassenpfads.
19.2.4. Spring Security-Konfiguration
![]() |
Die Klasse [WebSecurityConfig] konfiguriert das Spring Security-Framework:
package hello;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/home").permitAll().anyRequest().authenticated();
http.formLogin().loginPage("/login").permitAll().and().logout().permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
}
- Zeile 9: Die Annotation [@Configuration] macht die Klasse [WebSecurityConfig] zu einer Konfigurationsklasse;
- Zeile 10: Die Annotation [@EnableWebSecurity] macht die Klasse [WebSecurityConfig] zu einer Spring Security-Konfigurationsklasse;
- Zeile 11: Die Klasse [WebSecurity] erweitert die Klasse [WebSecurityConfigurerAdapter], um bestimmte Methoden zu überschreiben;
- Zeile 12: Neudefinition einer Methode aus der übergeordneten Klasse;
- Zeilen 13–16: Die Methode [configure(HttpSecurity http)] wird überschrieben, um Zugriffsrechte für die verschiedenen URLs der Anwendung zu definieren;
- Zeile 14: Die Methode [http.authorizeRequests()] ermöglicht es, URLs mit Zugriffsrechten zu verknüpfen. Dort werden folgende Zuordnungen vorgenommen:
Regel | Code | |
Zugriff ohne Authentifizierung | | |
Nur authentifizierter Zugriff |
- Zeile 15: definiert die Authentifizierungsmethode. Die Authentifizierung erfolgt über ein URL-Formular [/login], das für alle zugänglich ist [http.formLogin().loginPage("/login").permitAll()]. Auch die Abmeldung ist für alle zugänglich;
- Zeilen 19–21: definieren die Methode [configure(AuthenticationManagerBuilder auth)] neu, die Benutzer verwaltet;
- Zeile 20: Die Authentifizierung erfolgt über fest programmierte Benutzer [auth.inMemoryAuthentication()]. Ein Benutzer wird hier mit dem Login [user], dem Passwort [password] und der Rolle [USER] definiert. Benutzern mit derselben Rolle können dieselben Berechtigungen erteilt werden;
19.2.5. Ausführbare Klasse
![]() |
Die [Application]-Klasse sieht wie folgt aus:
package hello;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@EnableAutoConfiguration
@Configuration
@ComponentScan
public class Application {
public static void main(String[] args) throws Throwable {
SpringApplication.run(Application.class, args);
}
}
- Zeile 8: Die Annotation [@EnableAutoConfiguration] weist Spring Boot (Zeile 3) an, die Konfiguration durchzuführen, die der Entwickler nicht explizit eingerichtet hat;
- Zeile 9: macht die Klasse [Application] zu einer Spring-Konfigurationsklasse;
- Zeile 10: weist das System an, das Verzeichnis mit der [Application]-Klasse nach Spring-Komponenten zu durchsuchen. Die beiden Klassen [MvcConfig] und [WebSecurityConfig] werden somit gefunden, da sie die Annotation [@Configuration] tragen;
- Zeile 13: die [main]-Methode der ausführbaren Klasse;
- Zeile 14: Die statische Methode [SpringApplication.run] wird mit der Konfigurationsklasse [Application] als Parameter ausgeführt. Wir sind diesem Vorgang bereits begegnet und wissen, dass der in den Maven-Abhängigkeiten des Projekts eingebettete Tomcat-Server gestartet und das Projekt darauf bereitgestellt wird. Wir haben gesehen, dass vier URLs verarbeitet wurden [/, /home, /login, /hello] und dass einige durch Zugriffsrechte geschützt waren.
19.2.6. Testen der Anwendung
Beginnen wir mit der Anfrage der URL [/], die eine der vier akzeptierten URLs ist. Sie ist mit der Ansicht [/templates/home.html] verknüpft:
![]() |
Die angeforderte URL [/] ist für jeden zugänglich. Deshalb konnten wir sie abrufen. Der Link [hier] lautet wie folgt:
Die URL [/hello] wird aufgerufen, wenn wir auf den Link klicken. Diese ist geschützt:
Regel | Code | |
Zugriff ohne Authentifizierung | | |
Nur authentifizierter Zugriff |
Sie müssen authentifiziert sein, um darauf zugreifen zu können. Spring Security leitet den Browser des Clients dann auf die Authentifizierungsseite weiter. Basierend auf der gezeigten Konfiguration ist dies die Seite unter der URL [/login]. Diese Seite ist für alle zugänglich:
http.formLogin().loginPage("/login").permitAll().and().logout().permitAll();
So erhalten wir sie [1]:
![]() |
Der Quellcode der abgerufenen Seite lautet wie folgt:
- In Zeile 7 erscheint ein verstecktes Feld, das auf der ursprünglichen Seite [login.html] nicht vorhanden ist. Thymeleaf hat es hinzugefügt. Dieser Code, bekannt als CSRF (Cross-Site Request Forgery), dient dazu, eine Sicherheitslücke zu schließen. Dieses Token muss zusammen mit den Anmeldedaten an Spring Security zurückgesendet werden, damit die Authentifizierung akzeptiert wird;
Wir erinnern uns, dass von Spring Security nur das Paar aus Benutzername und Passwort erkannt wird. Wenn wir in [2] etwas anderes eingeben, erhalten wir dieselbe Seite mit einer Fehlermeldung in [3]. Spring Security hat den Browser auf die URL [http://localhost:8080/login?error] umgeleitet. Das Vorhandensein des Parameters [error] hat die Anzeige des Tags ausgelöst:
<div th:if="${param.error}">Invalid username and password.</div>
Geben wir nun die erwarteten Werte für Benutzername und Passwort ein [4]:
![]() |
- in [4] melden wir uns an;
- in [5] leitet uns Spring Security zur URL [/hello] weiter, da dies die URL ist, die wir angefordert haben, als wir zur Anmeldeseite weitergeleitet wurden. Die Identität des Benutzers wurde durch die folgende Zeile in [hello.html] angezeigt:
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
Seite [5] zeigt das folgende Formular an:
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out" />
</form>
Wenn Sie auf die Schaltfläche [Abmelden] klicken, wird eine POST-Anfrage an die URL [/logout] gesendet. Wie die URL [/login] ist auch diese URL für jeden zugänglich:
http.formLogin().loginPage("/login").permitAll().and().logout().permitAll();
In unserer URL-/View-Zuordnung haben wir für die URL [/logout] nichts definiert. Was wird passieren? Probieren wir es aus:
![]() |
- In [6] klicken wir auf die Schaltfläche [Abmelden];
- in [7] sehen wir, dass wir zur URL [http://localhost:8080/login?logout] weitergeleitet wurden. Spring Security hat diese Weiterleitung angefordert. Das Vorhandensein des Parameters [logout] in der URL führte dazu, dass die folgende Zeile in der Ansicht angezeigt wurde:
<div th:if="${param.logout}">You have been logged out.</div>
19.2.7. Fazit
Im vorangegangenen Beispiel hätten wir die Webanwendung zuerst schreiben und sie dann sichern können. Spring Security ist nicht-intrusiv. Sie können Sicherheit für eine bereits geschriebene Webanwendung implementieren. Darüber hinaus haben wir folgende Punkte festgestellt:
- Es ist möglich, eine Authentifizierungsseite zu definieren;
- die Authentifizierung muss mit dem von Spring Security ausgegebenen CSRF-Token erfolgen;
- Wenn die Authentifizierung fehlschlägt, werden Sie mit einem zusätzlichen Fehlerparameter in der URL auf die Authentifizierungsseite weitergeleitet;
- Wenn die Authentifizierung erfolgreich ist, werden Sie zu der Seite weitergeleitet, die zum Zeitpunkt der Authentifizierung angefordert wurde. Wenn Sie die Authentifizierungsseite direkt aufrufen, ohne eine Zwischenseite zu durchlaufen, leitet Spring Security Sie zur URL [/] weiter (dieser Fall wurde nicht demonstriert);
- Sie melden sich ab, indem Sie die URL [/logout] mit einer POST-Anfrage aufrufen. Spring Security leitet Sie dann mit dem Parameter „logout“ in der URL zur Authentifizierungsseite weiter;
All diese Schlussfolgerungen basieren auf dem Standardverhalten von Spring Security. Dieses Verhalten lässt sich durch Konfiguration ändern, indem bestimmte Methoden der Klasse [WebSecurityConfigurerAdapter] überschrieben werden.
Das vorherige Tutorial wird uns im weiteren Verlauf kaum helfen. Wir werden vielmehr Folgendes verwenden:
- eine Datenbank zum Speichern von Benutzern, deren Passwörtern und deren Rollen;
- eine HTTP-Header-basierte Authentifizierung;
Es gibt nur sehr wenige Tutorials zu dem, was wir hier tun wollen. Die Lösung, die wir vorschlagen, ist eine Kombination aus Code-Schnipseln, die wir hier und da gefunden haben.














