Spring Security mit einer Webanwendung (Tutorial)
Dieser Beitrag ist Teil einer Serie über die Einführung in das Spring Framework und beschreibt den Einsatz von Spring Security in einer Konsolen- und Webanwendung.
Die Struktur des Projektes
Dieses Beispiel baut auf dem vorherigem Beispiel Spring mit einer Webanwendung mit JPA und Validierung in der Version 2.x auf. Die verwendeten Frameworks und Werkzeuge sind hier beschrieben. In diesem Beispiel werden die folgenden Technologien des Spring Frameworks vorgestellt:
- Die Einführung von Sicherheit (Security) in eine Spring Anwendung.
- Die Authentifizierung per Kommandozeile und per Webseite.
- Die Autorisierung von Services mit Annotationen.
- Das sichere Session-Management (Session Fixation Protection).
In der folgenden Bildergalerie sind die benötigten Bibliotheken für jedes einzelne Eclipse Projekt dargestellt.
Die Literaturempfehlungen für dieses Beispiel
- Referenzdokumentation des Spring Framework’s
- Spring 3: Framework für die Java-Entwicklung (*)
- Spring Security 3 (*)
Die Erweiterungen des Projektes test-spring-jpa
Wir beginnen mit der XML Konfiguration für das Projekt test-spring-jpa. Hier wird der zentrale AuthentificationManager
definiert. Er verwaltet verschiedene AuthenticationProvider
. Der Provider authentifiziert (Überprüfen der Benutzerkennung und Benutzerkennwortes) und autorisiert (Ermitteln der Berechtigungen) den Benutzer über seinen UserDetailsService
.
- In der Zeile 7 wird der Namespace für Spring Security hinzugefügt.
xmlns:context="http://www.springframework.org/schema/context" xmlns:sec="http://www.springframework.org/schema/security" xsi:schemaLocation="
- In den Zeilen 15 und 16 wird das Schema für Spring Security angegeben.
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
- Ab der Zeile 32 bis 43 wird der Authentifizierungsmanager mit dem Provider definiert. Hier werden die Authentifizierungsdaten und die Zugriffsrechte (Rollen) definiert. Die Kennwörter werden hier in diesem Beispiel im Klartext abgelegt.
- In der Zeile 47 wird der Import der speziellen XML Konfiguration für das Projekt
test-spring-jpa
angegeben. Diese Konfiguration gilt nur für denApplicationContext
in dem sie geladen wird.
<!-- Den Authentifikationsmanager definieren --> <sec:authentication-manager> <sec:authentication-provider> <sec:password-encoder hash="plaintext" /> <sec:user-service> <sec:user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" /> <sec:user name="frank" password="frank" authorities="ROLE_USER" /> </sec:user-service> </sec:authentication-provider> </sec:authentication-manager> <!-- Die projektspezifischen Konfigurationen laden --> <import resource="classpath:/META-INF/spring/db.xml" /> <import resource="classpath:/META-INF/spring/security.xml" />
Jetzt die neue XML Konfiguration für die Spring Security im Projekt test-spring-jpa
.
- In der Zeile 18 wird die Security definiert. Hier werden die Annotationen (
@PreAuthorize
,@PostAuthorize
,@PreFilter
und@PostFilter
) für die Security eingeschaltet. Diese können nicht mit der Annotation@Secured
kombiniert werden.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sec="http://www.springframework.org/schema/security" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd "> <description> Dieses ist die zentrale Konfiguration für die Security. </description> <!-- Die Annotations-basierende Security einschalten --> <sec:global-method-security mode="aspectj" pre-post-annotations="enabled" proxy-target-class="true" /> </beans>
Die Klasse Starter
muss um eine Authentifizierung des Benutzers erweitert werden.
- Die neuen Imports sind nicht dargestellt.
- Ab der Zeile 34 werden die Benutzerinformationen (Kennung und Kennwort) für die Authentifizierung abfragt.
- In der Zeile 49 wird die Authentifizierung und die Autorisierung am
AuthenticationManager
durchgeführt. - In der Zeile 50 wird bei erfolgreicher Authentifizierung die Autorisierung im
SecurityContext
über denSecurityContextHolder
am aktuellenThread
perThreadLocal
gebunden und steht so der Anwendung zu Verfügung.
try { // Einlesen der Authentisierungsdaten BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); System.out.println("Please enter your username:"); String name = in.readLine(); System.out.println("Please enter your password:"); String password = in.readLine(); // Authentifizierung des Benutzers AuthenticationManager authenticationManager = ctx.getBean(BeanIds.AUTHENTICATION_MANAGER, AuthenticationManager.class); Authentication request = new UsernamePasswordAuthenticationToken(name, password); Authentication result = authenticationManager.authenticate(request); SecurityContextHolder.getContext().setAuthentication(result); } catch (Exception exception) { logger.error("Fehler bei der Authentifizierung", exception); return; }
Der Service StandardDrivers
wird nun um die Autorisierung erweitert.
- In der Zeile 22 wird für den Zugriff auf alle Methoden dieses Services die Benutzerrolle USER verlangt.
/** * Die Standard-Implementierung des Services {@link Drivers}. * @author Frank W. Rahn */ @Service("drivers") @Transactional @PreAuthorize("hasRole('ROLE_USER')") public class StandardDrivers implements Drivers {
- In der Zeile 53 wird zusätzlich für das Erzeugen eines Fahrers die Benutzerrolle ADMIN verlangt.
@Override @PreAuthorize("hasRole('ROLE_ADMIN')") public Long createDriver(String name, String firstname) { Driver driver = new Driver(); driver.setName(name); driver.setFirstname(firstname); return create(driver); }
- In der Zeile 67 wird zusätzlich für das Erzeugen eines Fahrers die Benutzerrolle ADMIN verlangt.
@Override @PreAuthorize("hasRole('ROLE_ADMIN')") public Long create(Driver driver) {
Die Erweiterungen des Projektes test-spring-web
Ab hier beginnen wir mit den Erweiterungen am Deployment Discriptors der Webanwendung.
- Ab der Zeile 44 wird ein Servlet Filter für die Security definiert. Dieser Filter wirkt auf alle Requests an diesen Servlet Context.
- In der Zeile 52 wird der Name der Spring Bean definiert, welche die Kette der vordefinierten Servlet Filter für die Spring Security kennt. Typischerweise wird an Stelle des Init-Parameters der Filtername verwendet. Dieser Name muss dann
springSecurityFilterChain
lauten.
<filter> <description>Der Filter für die Security</description> <filter-name>securityFilter</filter-name> <filter-class> org.springframework.web.filter.DelegatingFilterProxy </filter-class> <init-param> <param-name>targetBeanName</param-name> <param-value>springSecurityFilterChain</param-value> </init-param> </filter> <filter-mapping> <filter-name>securityFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
- Ab der Zeile 77 wird ein Listener für das Abmelden des Benutzers definiert, wenn die HTTP Session ausläuft.
<listener> <description> Dieser Listener leitet die Event des HttpSessionListener an den ApplicationContext weiter. </description> <listener-class> org.springframework.security.web.session.HttpSessionEventPublisher </listener-class> </listener>
Nun die Änderung an der XML Konfiguration der fachlichen Spring Beans. Diese Basiert auf den Änderungen für das Projekt test-spring-jpa in diesem Artikel.
- Nicht dargestellt sind die Definitionen für den Namespace von Spring Security.
- Ab der Zeile 32 die Definitionen für den Authentifizierungsmanager, wie für das Projekt test-spring-jpa.
<!-- Den Authentifikationsmanager definieren --> <sec:authentication-manager> <sec:authentication-provider> <sec:password-encoder hash="plaintext" /> <sec:user-service> <sec:user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" /> <sec:user name="frank" password="frank" authorities="ROLE_USER" /> </sec:user-service> </sec:authentication-provider> </sec:authentication-manager>
- Ab der Zeile 46 werden die Definitionen für die spezielle Spring Security des HTTP Zugriffs hinzugefügt.
- In der Zeile 48 wird der Zugriff auf die Anmeldemaske für alle Benutzer freigegeben.
- In der Zeile 55 werden alle Seiten dieser Webanwendung für den Zugriff eines authentifizierten Benutzers mit der Rolle USER beschränkt. Der Ausdruck
hasRole()
kann verwendet werden, da dieses in der Zeile 46 eingeschaltet wurde. - In der Zeile 56 wird die Seite definiert, die im Fehlerfall aufgerufen wird. In diesem Fall wird wieder die Anmeldemaske angezeigt. Zusätzlich wird durch den Parameter
login_error=true
eine Fehlermeldung zum vorliegendem Fehlerfall ausgegeben. - In der Zeile 59 wird der Link für die Abmeldung definiert. Wenn dieser Link aufgerufen wird, wird der authentifizierte Benutzer vom System abgemeldet und auf die Anmeldemaske weitergeleitet.
- In der Zeile 63 wird die Seite definiert, die beim Timeout der Session aufgerufen wird. In diesem Fall auch wieder die Anmeldemaske. Durch den Parameter
timeout=true
wird eine entsprechende Fehlermeldung ausgegeben. - In der Zeile 64 wird die Session Fixation Protection über eine Migration der Session definiert. Dazu werden die Attribute aus der aktuellen Session gesichert und diese Session invalidiert (verworfen). Danach wird eine neue Session erzeugt und die Attribute in diese Session kopiert. Die ursprüngliche Session kann durch einen Angreifer nicht mehr verwendet werden.
- In der Zeile 67 wird die Anzahl der Anmeldungen pro Benutzer auf eine Session beschränkt. D. H. falls der Benutzer sich von einem anderem Arbeitsplatz anmelden möchte, erhält er eine Fehlermeldung.
<!-- Konfigurieren der HTTP Security --> <sec:http auto-config="true" use-expressions="true"> <!-- Zugriff auf /login für alle erlauben --> <sec:intercept-url pattern="/login" access="permitAll" /> <!-- Zugriff auf /** auf Benutzer mit der Rolle ROLE_USER einschränken --> <sec:intercept-url pattern="/**" access="hasRole('ROLE_USER')" /> <!-- Formular-basiertes Login konfigurieren --> <sec:form-login login-page="/login" authentication-failure-url="/login?login_error=true" /> <!-- Logout Seite definieren --> <sec:logout logout-url="/logout" /> <!-- Session Timeout Seite setzen und Session Fixation Attack Protection einschalten --> <sec:session-management invalid-session-url="/login?timeout=true" session-fixation-protection="migrateSession"> <!-- Maxmale Anzahl von Session per User (Doppelanmeldung) --> <sec:concurrency-control max-sessions="1" error-if-maximum-exceeded="true" /> </sec:session-management> </sec:http>
- In der Zeile 74 wird der Import der speziellen XML Konfiguration für die Security für das Projekt
test-spring-web
hinzugefügt.
<!-- Die projektspezifischen Konfigurationen laden --> <import resource="classpath:/META-INF/spring/db.xml" /> <import resource="classpath:/META-INF/spring/security.xml" /> <import resource="classpath:/de/rahn/validation/validation.xml" />
Im DriversController
wird über eine Erweiterung des Logings, ab der Zeile 45, der angemeldete Benutzer ausgegeben.
@ModelAttribute("driver") public Driver handleRequest(@RequestParam(required = false) Long id) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Object principal = authentication == null ? null : authentication.getPrincipal(); logger .info( "Die Methode DriversController.handleRequest() wurde aufgerufen. id={}, user={}", id, principal); if (id != null) {
Für das Anmelden muss ein neuer Controller erstellt werden.
package de.rahn.web.spring; import static org.slf4j.LoggerFactory.getLogger; import org.slf4j.Logger; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** * Der Controller für das Login und Logout. * @author Frank W. Rahn */ @Controller public class LoginLogoutController { private static final Logger logger = getLogger(LoginLogoutController.class); /** * Die Methode für das Login. */ @RequestMapping("/login") public void login() { logger.info("Benutzer hat die Anmeldeseite aufgerufen."); } }
Für das Abmelden muss ein zusätzlicher Link in die Startseite aufgenommen werden.
- In der Zeile 2 wird die JavaServer Pages Standard Tag Library hinzugefügt.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
- Ab der Zeile 18 wird der zusätzliche Link aufgebaut und ausgegeben.
<c:url var="logoutUrl" value="/logout" /> <li><a href="${logoutUrl}">Abmelden</a></li>
Zum Schluss noch die Formular-basierende Anmeldemaske für die Webanwendung.
- In der Zeile 11 wird der Fokus auf das erste Eingabefelde für die Benutzerkennung gesetzt.
- In den Zeilen 13 bis 19 wird die Fehlermeldung der Anmeldung behandelt.
- In den Zeilen 20 bis 26 wird die Meldung für das Ablaufen des Timeouts ausgegeben.
- In den Zeilen 28 bis 35 befindet sich das Formular für die Eingaben von Benutzerkennung und dem Kennwort. Die Eingaben werden durch das Standard-Servlet
j_spring_security_check
verarbeitet.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ page import="org.springframework.security.web.WebAttributes" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Anmeldung | Frank W. Rahn</title> </head> <body onload="document.login.j_username.focus();"> <h1>Anmeldung</h1> <c:if test="${not empty param.login_error}"> <fieldset> <legend>Fehler bei Login</legend> <div style="color: red;">${SPRING_SECURITY_LAST_EXCEPTION.message}</div> </fieldset> <br/> </c:if> <c:if test="${not empty param.timeout}"> <fieldset> <legend>Timeout</legend> <div style="color: red;">Ihre Benutzersitzung ist abgelaufen oder wurde beendet.</div> </fieldset> <br/> </c:if> <c:url var="url" value="j_spring_security_check" /> <form action="${url}" method="post"> <fieldset> <p><label for="j_username">Benutzerkennung: </label><input type="text" id="j_username" name="j_username" /></p> <p><label for="j_password">Kennwort: </label><input type="password" id="j_password" name="j_password" /></p> <input name="submit" type="submit" value="Anmelden" /> <input name="reset" type="reset" value="Zurücksetzen" /> </fieldset> </form> </body> </html>
Die Masken
In folgender Bildgalerie werden die Screenshots der Masken der Webanwendung im Browser dargestellt.
Der Quellcode und Download des Beispiels
Den Quellcode ansehen bei GitHub:
Spring mit JPA und Hibernate
Spring mit einer einfachen Webanwendung
Der Download einer ZIP-Datei von GitHub:
Spring mit JPA und Hibernate
Spring mit einer einfachen Webanwendung
Update am 28.09.2012
Es wurden folgende Änderungen an allen Projekten vorgenommen.
- Die Projekte wurden aus meinem lokalen Apache Subversion Repository in meine öffentlichen GitHub Repositories verschoben.
- Projekt test-spring-simple
- Projekt test-spring-jpa
- Projekt test-spring-web
- Die folgenden typischen Anpassungen an Git wurden an allen Projekten durchgeführt.
.directory
gelöscht.gitignore
hinzugefügtREADME.md
hinzugefügtCOPYRIGHT.md
hinzugefügt- In jedem Repository wurde für jeden Beitrag der Serie ein Branch angelegt.
develop-spring-an-einem-einfachen-beispiel
develop-spring-mit-aop
develop-spring-mit-jpa-und-hibernate
develop-spring-mit-einer-einfachen-webanwendung
develop-spring-mit-einer-webanwendung-mit-jpa-und-validierung
develop-spring-security-mit-einer-webanwendung
develop-spring-mit-restful-webservice
Updates von 06.10.2012 bis zum 14.10.2012
Es wurden folgende Änderungen an allen Projekten vorgenommen.
- Aktualisierung des OpenJDK auf die Version 1.7.0.
- Aktualisierung der Entwicklungsumgebung Eclipse auf die Version 4.2.1.
Die benötigten Plugins aus dem Eclipse Marketplace: - Aktualisierung der Datei
pom.xml
:- Anpassungen an die OpenJDK Version
- GitHub Einträge (SCM und URL) hinzugefügt
- Aktualisierung der Libraries auf aktuellerer Versionen (z. B. Spring Version 3.1.2.RELEASE, Hibernate Version 4.1.7.Final, JUnit Version 4.10, mockito Version 1.9.0, …)
Die genaueren Versionen bitte aus den jeweiligenpom.xml
auf GitHub entnehmen.
- Wer ist der optimale Java Bean Mapper? - Freitag, 22. September 2023
- Spring Boot Webanwendung: Die ersten Schritte (Tutorial) - Montag, 28. März 2016
- Mainframe-Zugriff via Java - Sonntag, 04. Mai 2014
Hinterlasse einen Kommentar
An der Diskussion beteiligen?Hinterlasse uns deinen Kommentar!