Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren...

65
Craig Walls, Ryan Breidenbach Spring im Einsatz Übersetzt aus dem Englischen von Christian Alkemper, Jürgen Dubau ISBN-10: 3-446-41240-9 ISBN-13: 978-3-446-41240-8 Vorwort Weitere Informationen oder Bestellungen unter http://www.hanser.de/978-3-446-41240-8 sowie im Buchhandel

Transcript of Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren...

Page 1: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

Craig Walls, Ryan Breidenbach

Spring im EinsatzÜbersetzt aus dem Englischen von Christian Alkemper, Jürgen

Dubau

ISBN-10: 3-446-41240-9ISBN-13: 978-3-446-41240-8

Vorwort

Weitere Informationen oder Bestellungen unterhttp://www.hanser.de/978-3-446-41240-8

sowie im Buchhandel

Page 2: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

XV

Vorwort

Es war der 7. Dezember 2005. Ich lehnte an der Wand des großen Sitzungssaals eines Hotels in Miami Beach, Florida. Der Saal war voll von Entwicklern aus aller Welt, die ihre wunderbaren Sandstrände von Floridas Hauptstadt aus einem ganz bestimmten Grund auf-gegeben und sich hier versammelt hatten: um über Spring zu reden. Wie soll ich es beschreiben? Ich kam mir vor wie in einem Irrenhaus. Statt Sonne und Brandung zu genießen, waren wir alle in einem geschlossenen Raum zusammengeströmt – im matten Widerschein unserer Laptopbildschirme sitzend und begierig, von jenen Wissen-den mehr über unser geliebtes Framework zu erfahren. An jenem Abend hingen wir an den Lippen von Rod Johnson – seines Zeichens „Vater“ von Spring –, der die Konferenz mit einer Grundsatzrede eröffnete. Er sprach von den Anfängen und den großen Erfolgen von Spring. Danach rief er einige Mitglieder des Spring-Teams auf das Podium, um die in der nächsten Version zur Verfügung stehenden neuen Features vorstellen zu lassen. Johnson hatte noch nicht lange gesprochen, als er eine Ankündigung machte, die alle An-wesenden aufhorchen ließ. Wir alle waren davon ausgegangen, dass diese fantastischen neuen Funktionalitäten Bestandteil von Spring 1.3 sein würden – der unserer Meinung nach nächsten Version. Zu unser aller Überraschung teilte Johnson jedoch mit, dass es Spring 1.3 nie geben würde; die nächste Version sei bereits Spring 2.0. Es ist keine einfache Entscheidung, den nächsten Release eines Projekts mit einer völlig neuen Hauptversionsnummer zu versehen. Auch bei Spring stellte ein solcher Vorgang einen bemerkenswerten Fortschritt dar. Wenn die nächste Spring-Version die Nummer 2.0 trug, so war mit grundsätzlichen Erweiterungen zu rechnen. Und tatsächlich erschien zehn Monate später Spring 2.0 – mit neuen Funktionalitäten in Hülle und Fülle:

vereinfachte XML-Konfiguration und die Möglichkeit, benutzerdefinierte Konfigura-tionselemente zu erstellen

erheblich vereinfachte AOP und Transaktionen

Unterstützung von Java 5-Annotationen zur Deklaration von Aspekten, Transaktionen und erforderlichen Bean-Eigenschaften

Page 3: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

Vorwort

XVI

Funktionalität zur Erstellung von Beans aus Skripts, die in JRuby, Groovy oder Bean-Shell geschrieben sind

neue JDBC-Vorlagen zur Unterstützung benannter Parameter und von Java 5-Features

verbesserte JMS-Unterstützung einschließlich eines asynchronen Nachrichtenempfangs (zur Erstellung nachrichtengetriebener POJOs)

eine neue formularbindende JSP-Tag-Bibliothek

verschiedene Convention-over-Configuration-Verbesserungen zur Verringerung des zur Spring-Konfiguration erforderlichen XML-Codes

JPA-Unterstützung (Java Persistence API)

verbesserte Bereichsdefinition für Beans, einschließlich der Festlegung von Anforde-rungs- und Sitzungsbereichen von Beans für Webanwendungen

Möglichkeit der Durchführung von Dependency Injections bei Objekten, die Spring nicht erstellt (z. B. Domänenobjekte)

Johnson ließ in seiner Rede verlauten, dass die Bedeutung der neuen Features den Sprung auf die Versionsnummer 2.0 rechtfertigten. Das war aber noch nicht alles. Neben der Arbeit am Kern-Framework von Spring waren auch einige spezifische Projekte auf den Weg gebracht worden, die Spring um weitere Funktionalitäten ergänzen. Hierzu gehören die folgenden:

Spring Web Flow. Basiert auf Spring MVC und ermöglicht die Entwicklung ablauf-basierter Webanwendungen.

XFire. Gestattet den Export von Spring-Beans als SOAP-Webdienste.

Spring-WS. Ermöglicht die Erstellung von Contract-First-Webdiensten.

Spring Modules. Bietet u. a. deklaratives Caching und Validierung.

DWR (Direct Web Remoting). Macht Spring-Beans Ajax-fähig.

Lingo. Erlaubt das asynchrone Aufrufen von Methoden für Remote-Beans. So wurde auch mir klar: Was – wenn nicht all diese Fortschritte – würde eine zweite Auf-lage von Spring in Action rechtfertigen? Bei Manning dachte man wohl ähnlich. Ein gutes Jahr später liegt es nun vor Ihnen: das lang erwartete Update von Spring in Action, in dem wir viele neue Features von Spring 2.0 behandeln. Es hat etwas länger gedauert als geplant, doch ich hoffe, dass sich das Warten gelohnt hat. Ich möchte damit das Gleiche bezwecken wie mit der ersten Auflage: Ihnen meinen Spaß bei der Entwick-lung in Spring zu vermitteln. Hoffentlich vermag dieses Buch Ihr Vergnügen an dieser Technologie noch zu steigern.

Page 4: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

Vorwort zur ersten Auflage

XVII

Vorwort zur ersten Auflage

Softwareentwickler benötigen gewisse Eigenschaften, um ihr Handwerk erfolgreich auszu-führen. Zunächst einmal müssen sie Fähigkeiten zur logischen Analyse und zur Problem-lösung mitbringen. Die Rolle eines Entwicklers besteht in erster Linie darin, Software zu ent-wickeln, die Unternehmensprobleme löst. Insofern muss er in der Lage sein, die Kundenbe-dürfnisse zu analysieren und ebenso zweckdienliche wie kreative Lösungen zu präsentieren. Entwickler müssen aber auch neugierig sein. Neuentwicklungen in der Softwareindustrie sind bewegliche Ziele – sie entwickeln sich fortlaufend weiter. Ständig entstehen neue Frameworks, Technologien, Sprachen und Methodiken. Und jede Innovation ist ein neues Werkzeug, das beherrscht und zum eigenen Inventar hinzugefügt werden will, damit man seine Aufgaben besser und auch schneller erledigen kann. Und dann gibt es noch die meistgeschätzte Eigenschaft von allen: die Faulheit. Es handelt sich dabei um jene Art von Faulheit, die Entwickler zur harten Arbeit anspornt, um Lösun-gen mit geringstmöglichem Aufwand zu finden. Neugier, ein gerüttelt Maß an solcher Faulheit und umfassende analytische Fähigkeiten waren es, die die Autoren vor vier Jahren zusammenführten, um neue Möglichkeiten der Softwareentwicklung zu entdecken. Damals nahm die Verfügbarkeit von Open-Source-Software in der Java-Gemeinde ein kri-tisches Ausmaß an. In der gesamten Java-Landschaft erblühten unzählige Open-Source-Frameworks. Unser Wunsch-Framework sollte unsere Bedürfnisse quasi kennen – also 80 Prozent unserer Anforderungen out of the box erfüllen. Ein solches Framework musste zudem unkompliziert erweiterbar sein, damit man Funktionalitäten, die nicht direkt ver-fügbar waren, postwendend nachrüsten konnte. Dabei bedeutete „erweiterbar“ nicht irgend-welche Tricks, um sich hinterher richtig schäbig zu fühlen – vielmehr ging es um das elegante Erweitern. Das ist doch nicht zuviel verlangt, oder? Das erste dieser Frameworks, das wir als Team vom Start weg benutzten, war Ant. Wir konnten unmittelbar erkennen, dass Ant von einem anderen Entwickler erstellt worden war, der wusste, wie aufwändig die Entwicklung von Java-Anwendungen ist. Von diesem Moment an gab es kein javac mehr. Auch keinen CLASSPATH. Und all dies mit einer unkomplizierten (wenn auch gelegentlich etwas langatmigen) XML-Konfiguration. Ent-wicklungen wurden zusehends einfacher. Das Leben kann so schön sein.

Page 5: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

Vorwort zur ersten Auflage

XVIII

Im Laufe der Zeit setzten wir immer mehr Tools ein. Eclipse wurde die IDE unserer Wahl, Log4J das Standard-Toolkit für die Protokollierung (nicht nur bei uns). Am Ende wurde auch unsere kommerzielle Suchlösung verdrängt – durch Lucene. All diese Tools erfüllten unsere Anforderungen und waren gleichzeitig benutzerfreundlich, verständlich und einfach zu erweitern. Doch irgendetwas fehlte. Diese tollen Tools waren entwickelt worden, um die Software-entwicklung zu unterstützen (Ant, Eclipse) oder eine sehr spezielle Funktionalität bereitzu-stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen: Persistenz, Transaktionen und die Inte-gration mit anderen Unternehmensressourcen. All dies änderte sich im vergangenen Jahr, als wir die unternehmensspezifisch bemerkens-werte Leistungsfähigkeit von Spring und Hibernate entdeckten. Mit diesen beiden Frame-works waren praktisch alle unsere Bedürfnisse in punkto Middle Tier und Data Tier erfüllt. Zuerst arbeiteten wir uns in Hibernate ein. Es war das intuitivste und funktionsreichste Tool für das O/R-Mapping, das überhaupt zu bekommen war, doch erst nach der Mitein-beziehung von Spring sah unser Code richtig attraktiv aus. Mit der Dependency Injection in Spring waren wir aller benutzerdefinierten Fabriken und Konfiguratoren ledig. In der Tat war dies anfangs auch der Grund dafür, Spring in unsere Anwendungen zu integrieren. Das Wiring gestattete uns die Optimierung unserer Anwendungskonfigurationen ohne Zu-hilfenahme selbst ausgetüftelter Lösungen. (Nun gut: Entwickler lieben es, eigene Frame-works zu erstellen. Aber man muss doch auch mal loslassen können!) Sehr schnell entdeckten wir eine nette Draufgabe: Die Integration von Spring und Hiberna-te war kinderleicht. Auf diese Weise konnten wir unsere selbstdefinierten Hibernate-Integrationsklassen in der Schublade verschwinden lassen und stattdessen die Unterstüt-zung durch Spring verwenden. Außerdem verwies uns dieses Feature direkt auf die Spring-Unterstützung für transparente Persistenz. Sehen genau hin: Erkennen Sie das Muster? Je umfassender wir Spring einsetzten, umso mehr neue Features entdeckten wir. Und bei jedem einzelnen dieser Features war es eine Freude, damit zu arbeiten. Das MVC-Web-Framework funktionierte in so mancher An-wendung hervorragend. Auch der AOP-Support war insbesondere im sicherheitstechni-schen Bereich an vielen Stellen richtig praktisch. Die JDBC-Unterstützung stellte sich bei kleineren Programme als recht angenehm heraus. Ach ja: Für die Zeitplanung setzen wir sie ebenfalls ein. Und der JNDI-Zugriff. Und die Mailintegration. Wenn es darum geht, die geheimen Wünsche von Entwicklern Wirklichkeit werden zu lassen, erweist sich Spring als echtes Füllhorn. Wir mochten Spring so sehr, dass wir meinten, jemand müsse ein Buch darüber schreiben. Zum Glück hatte einer von uns bereits einen Titel für Manning geschrieben und kannte die entsprechende Vorgehensweise. Deswegen war auch relativ schnell klar, dass dieser „Je-mand“ wir sein würden. Wir haben uns dieses Projekts angenommen, um das Wort von Spring hinaus in die Welt zu tragen. Mit Spring zu arbeiten, macht wirklich Spaß. Ihnen wird es bald nicht anders ergehen. Wir hoffen, dass dieses Buch ein angenehmer Begleiter sein wird, der Sie zu diesem Punkt führt.

Page 6: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

XIX

Danksagung

Wow! Dieses Buch zu schreiben hat doch wesentlich mehr Zeit in Anspruch genommen als ursprünglich angenommen, und ohne die Hilfe, Inspiration und Unterstützung der fan-tastischen Leute hinter den Kulissen würden Sie es definitiv nicht in Ihren Händen halten. Zunächst möchte ich den hart schuftenden Mitarbeitern von Manning meine Anerkennung aussprechen. Irgendwie ist es ihnen auf wundersame Weise gelungen, aus meinem schlam-pig geschriebenen Manuskript ein ansehnliches Stück Programmierliteratur zu machen. Als da wären: Marjan Bace, Mary Piergies, Cynthia Kane, Dottie Marsico, Karen Tegt-meyer, Leslie Haimes, Liz Welch, Gabriel Dobrescu, Ron Tomich, Kerri Bonasch, Jackie Carter, Frank Blackwell, Michael Stephens und Benjamin Berg. Ferner möchte ich meinen Lektoren danken. Sie haben sich die Zeit genommen, um die bei einem solchen Buch erforderliche konstruktive Kritik zu formulieren: Doug Warren, Olivier Jolly, Matthew Payne, Bill Fly, Jonathon Esterhazy, Philip Hallstrom, Mark Chai-mungkalanont, Eric Raymond, Dan Allen, George M. Jempty, Mojahedul Hasanat, Vlad Kofman, Ashik Uzzaman, Norman Richards, Jeff Cunningham, Stuart Caborn, Patrick Dennis, Bas Vodde und Michael Masters. Erik Weibust und Valentin Crettaz führten außerdem ein zweites technisches Lektorat des Manuskripts unmittelbar vor der Druck-legung durch. Dann waren da Leute, die zwar nicht direkt an dem Buch mitarbeiteten, deren Einfluss auf mich bzw. dieses Buch aber nicht zu unterschätzen ist. Zu ihnen gehören: Meine Gefährtin, liebende Partnerin und die schönste Frau der Welt: Raymie. Danke für Deine Geduld bei einem weiteren, scheinbar nie enden wollenden Buchprojekt. Es tut mir leid, dass es so lange gedauert hat. Nun werde ich wieder häufiger Blumen mitbringen, und wir werden wieder ausgehen. Ach ja: Auch den Rasen werde ich wieder mähen. Meine süßen, hinreißenden kleinen Mädchen: Maisy und Madison. Danke für eure Umar-mungen, euer Lachen und die Stunden mit euch, in denen ich mich so herrlich vom Schrei-ben entspannen konnte. Ryan Breidenbach, meinem Koautor der ersten Auflage, vielen Dank, dass Du dieses Pro-jekt mit angeschoben und mir bei der zweiten Auflage so viel Feedback vermittelt hast.

Page 7: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

Danksagung

XX

Dank dem Spring-Team. Kein Teil dieses Buchs wäre möglich (geschweige denn nötig) gewesen, hättet ihr nicht die Vision und den Ehrgeiz gehabt, ein so tolles Framework zu entwickeln. Insbesondere danke ich Rod Johnson und Colin Sampaleanu für ihre Anmer-kungen zu meinem Blog und die Chatsessions – auf diese Weise konnte ich mein Denken in die richtigen Bahnen lenken. Ferner möchte ich Arjen Poutsma für das Gegenlesen des Kapitels zu Spring-WS und dafür danken, dass er mich in Schach gehalten hat. Ich danke allen, mit denen ich in den letzten Jahren zusammengearbeitet habe. Durch die Arbeit mit euch habe ich viel wertvolles Wissen erworben. Danke für eure Professionalität, eure Hingabe und eure Freundschaft: Jeff Hanson, Jim Wallace, Don Beale, Van Panya-nouvong, James Tikalsky, Ryan Breidenbach, Marianna Krupin, Tonji Zimmerman, Jeff Wellen, Chris Howard, Derek Lane, Tom McGraw, Greg Vaughn, Doug Warren, Jon West, Peter Presland-Byrne, Ravi Varanasi, Srinivasa Penubothu, Gary Edwards, Greg Helton, Jacob Orshalick, Valerie Crowley, Tyler Osborne, Stephanie Co, Maggie Zhuang, Tim Sporcic, William Johnson, John Moore, Brian Eschbach, Chris Morris, Dave Sims, Andy Cline, Bear Cahill, Greg Graham und Paul Nelson. Nicht zu vergessen all meine anderen Freunde, Kollegen, Co-Nerds, all jene, die ich auf Konferenzen getroffen habe, die Mitglieder meiner Linked-In-Liste und diejenigen, die mich genötigt haben, ihren Namen in dieser Danksagung aufzuführen: James Bell, Daniel Brookshier, Scott Davis, Ben Galbraith, Bill Fly, Justin Gehtland, Pete Gekas, Robert Gleaton, Stu Halloway, Erik Hatcher, Rick Hightower, Ramnivas Laddad, Guillaume Laforge, Crazy Bob Lee, Ted Neward, Matt Raible, Leo Ramirez, Arun Rao, Norman Richards, Chris Richardson, James Strachan, Bruce Tate, Glenn Vanderburg, Becca Whee-ler und Jay Zimmerman. Zum Schluss möchte ich noch Jack Bauer meine unendliche Dankbarkeit dafür ausspre-chen, dass er die Welt rettet – und das rund um die Uhr.

Page 8: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

Zu diesem Buch

XXI

Zu diesem Buch

Die Entwicklung von Spring verfolgte ein ganz bestimmtes Ziel: Das Framework sollte die Entwicklung von JEE-Anwendungen vereinfachen. In diesem Sinne wurde auch Spring im Einsatz geschrieben, um das Erlernen von Spring zu erleichtern. Ich beabsichtige nicht, die vielen Spring-APIs Schlag auf Schlag aufzulisten. Stattdessen stelle ich – so hoffe ich zumindest – das Spring-Framework in einer Form dar, mit der ein JEE-Entwickler etwas anfangen kann: äußerst praxisorientiert und mit zahlreichen Codebeispielen versehen. Da Spring ein modulares Framework ist, ist auch dieses Buch so aufgebaut. Ich weiß natürlich, dass nicht alle Entwickler dieselben Anforderungen stellen. Manche wollen das Spring-Framework von Grund auf erlernen, andere ziehen es hingegen vor, sich die The-men je nach Bedarf zu Gemüte zu führen, und dies mit der ihnen eigenen Lerngeschwin-digkeit. Unser Buch lässt sich auf beiderlei Weise verwenden – es eignet sich für den erst-maligen Einstieg in Spring ebenso wie als Leitfaden und Referenzhandbuch für all jene, die sich mit bestimmten Details näher beschäftigen wollen.

Der Ablauf Spring im Einsatz umfasst drei Teile und zwei Anhänge. Jeder dieser drei Teile legt den Schwerpunkt auf einen allgemeinen Bereich des Spring-Frameworks: das Kern-Frame-work, die Business- und Datenschichten sowie die Darstellungsschicht. Jeder Teil baut zwar inhaltlich in gewisser Weise auf dem vorhergehenden auf, kann aber auch für sich stehen und erlaubt es so, sich auf ein Thema zu stürzen, ohne die vorangegangenen Teile gelesen zu haben. In Teil 1 behandeln wir die beiden Kernfunktionalitäten des Spring-Frameworks: die De-pendency Injection (DI) und die aspektorientierte Programmierung (AOP). Hier erhalten Sie einen umfassenden Überblick über die Grundlagen von Spring. Diese werden wir im weiteren Verlauf des Buches noch benötigen. Kapitel 1 stellt Ihnen DI und AOP vor und zeigt, wie mithilfe dieser Konzepte lose gekop-pelte Java-Anwendungen erstellt werden können. In Kapitel 2 untersuchen wir dann genauer, wie Sie Ihre Anwendungsobjekte mithilfe der DI konfigurieren und assoziieren. Sie lernen, wie man lose gekoppelte Komponenten

Page 9: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

Zu diesem Buch

XXII

schreibt und ihre Abhängigkeiten und Eigenschaften innerhalb des Spring-Containers via XML verschaltet. Sobald Sie über die Grundlagen zur Bean-Verschaltung Bescheid wissen, stellen wir in Kapitel 3 einige fortgeschrittene Features des Spring-Containers vor. Unter anderem erfah-ren Sie, wie man sich in den Lebenszyklus Ihrer Anwendungskomponenten einhängt, Ord-nungsbeziehungen für Ihre Bean-Konfigurationen erstellt und in Ruby und Groovy ge-schriebene Skriptkomponenten einbindet. Kapitel 4 untersucht, wie man mithilfe der AOP-Funktionalität in Spring CCCs von den Objekten entkoppelt, die sie bedienen. Ferner legt dieses Kapitel die Grundlage für spätere Abschnitte, in denen Sie die Spring-AOP einsetzen, um deklarative Dienste wie Transakti-onen, Sicherheit und Caching bereitzustellen. Teil 2 baut auf den in Teil 1 vorgestellten DI- und AOP-Features auf und zeigt, wie man diese Konzepte in den Daten- und Business-Tiern Ihrer Anwendung einsetzt. Kapitel 5 behandelt die Spring-seitige Unterstützung der Datenpersistenz. So werden Sie etwa den JDBC-Support in Spring kennenlernen; mit ihm lässt sich ein Großteil des für JDBC typischen Bausteincodes entfernen. Ferner lesen Sie hier, wie Spring mit diversen verbreiteten Persistenz-Frameworks wie Hibernate, iBATIS und der Java Persistence API (JPA) integriert werden kann. Kapitel 6 ergänzt Kapitel 5 und zeigt Ihnen, wie man die Integrität Ihrer Datenbank mit-hilfe der Transaktionsunterstützung in Spring sicherstellt. Sie erfahren, wie Spring mit der AOP einfachen Anwendungsobjekten die Leistungsfähigkeit deklarativer Transaktionen verleiht. In Kapitel 7 lernen Sie, wie Sie Ihre Anwendung mit Spring Security sicher gestalten. Sie erfahren, wie Spring Security-Anwendungen mit Servlet-Filtern auf der Webanforderungs-ebene und mit der Spring-AOP auf der Methodenebene absichert. Kapitel 8 untersucht die Möglichkeit, Anwendungsobjekte als Remotedienste bereitzustel-len. Außerdem erfahren Sie, wie man auf Remotedienste genauso einfach zugreift wie auf irgendein anderes Objekt Ihrer Anwendung (zu den Remote-Technologien, die wir dort behandeln, gehören RMI, Hessian/Burlap, SOAP-basierte Webdienste und der Spring-eigene HttpInvoker). Während Kapitel 8 die Webdienste in Spring behandelt, nimmt Kapitel 9 diesbezüglich einen anderen Standpunkt ein und beschreibt das Projekt Spring-WS. In diesem Kapitel erfahren Sie, wie Sie mit Spring-WS Contract-First-Webdienste erstellen, in denen der Vertrag des Dienstes von seiner Implementierung abgekoppelt ist. Kapitel 10 betrachtet die Verwendung von Spring zum Senden und Empfangen asynchro-ner Nachrichten mit JMS. Sie lernen hier nicht nur einfache JMS-Operationen kennen, sondern auch, wie man mit dem Open-Source-Projekt Lingo asynchrone Remotedienste über JMS zugänglich macht und verbraucht. Obwohl Spring die Notwendigkeit des EJB-Einsatzes weitgehend beseitigt, müssen Sie Spring und EJBs unter Umständen doch gemeinsam verwenden. Aus diesem Grund wid-met sich Kapitel 11 der Integration von Spring mit EJB. Sie erfahren dort, wie man Spring-

Page 10: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

Zu diesem Buch

XXIII

fähige EJBs schreibt und EJB-Referenzen in Ihrem Spring-Anwendungskontext verschal-tet, und lernen sogar, wie man EJB-artige Annotationen zur Konfiguration Ihrer Spring-Beans verwendet. Am Ende von Teil 2 zeigt Ihnen Kapitel 12, wie man mit Spring Aufträge plant, E-Mails versendet, auf JNDI-konfigurierte Ressourcen zugreift und Ihre Anwendungsobjekte mit JMX verwaltet. In Teil 3 verschiebt sich die Diskussion ein wenig weiter in Richtung Endbenutzer. Wir betrachten hier Ansätze, Spring zur Erstellung von Webanwendungen einzusetzen. Kapitel 13 stellt Ihnen das in Spring integrierte Web-Framework MVC vor. Sie erfahren, wie Spring Webparameter transparent an Ihre Businessobjekte bindet und gleichzeitig Validierung und Fehlerbehandlung bietet. Zudem werden Sie sehen, wie einfach es ist, Ihren Webanwendungen Funktionalitäten hinzuzufügen; hier kommt Springs reichhaltige Auswahl von Steuerelementen zum Zuge. Kapitel 14 nimmt den Faden des vorhergehenden Kapitels auf und behandelt die Ansichts-schicht von Spring MVC. In diesem Kapitel lesen Sie, wie Sie die Ausgabe eines Spring MVC-Steuerelements an einer bestimmten Ansichtskomponente zuordnen, um sie für den Benutzer darzustellen. Sie werden sehen, wie man Anwendungsansichten mithilfe von JSP, Velocity, FreeMarker und Tiles definiert, und erfahren, wie Nicht-HTML-Ausgabe-formate – beispielsweise PDF, Excel oder RSS – aus Spring MVC heraus generiert werden. Kapitel 15 untersucht Spring Web Flow, eine Erweiterung zu Spring MVC, die die Ent-wicklung dialogbasierter Webanwendungen gestattet. Sie erfahren in diesem Kapitel, wie man Webanwendungen erstellt, die den Benutzer durch einen bestimmten Ablaufvorgang geleiten. Kapitel 16 schließlich zeigt, wie man Spring mit anderen Web-Frameworks integriert. Wenn Sie bereits in ein anderes Web-Framework investiert haben (oder ein solches schlicht bevorzugen), ist dies das richtige Kapitel für Sie. Sie erfahren, wie Spring ver-schiedene populäre Web-Frameworks wie Struts, WebWork, Tapestry und JavaServer Faces (JSF) unterstützt. Anhang A enthält eine Einführung in die praktischen Aspekte von Spring. Hier zeigen wir Ihnen, wie Sie Spring herunterladen und in Ant und Maven 2 konfigurieren. Einer der wesentlichen Vorteile der losen Kopplung besteht in der vereinfachten Durchfüh-rung von Unit-Tests Ihrer Anwendungsobjekte. Anhang B zeigt Ihnen, wie Sie die DI und einige der testspezifischen Spring-Klassen beim Testen Ihrer Anwendungen zu Ihrem Vor-teil nutzen.

Zusätzliche Webinhalte Als ich dieses Buch schrieb, wollte ich so viel wie möglich über Spring hineinpacken. Dabei habe ich mich ein bisschen übernommen und am Ende mehr geschrieben, als in der Druckausgabe Platz hat. Wie bei so manchem Hollywoodfilm fiel auch hier viel Material dem Schnitt zum Opfer:

Page 11: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

Zu diesem Buch

XXIV

„Building Portlet Applications“ (Portlet-Anwendungen erstellen). Dieses Kapitel be-handelt das Spring Portlet MVC-Framework. Spring Portlet MVC ähnelt Spring MVC in auffälliger Weise (und rezykliert sogar einige der Klassen in Spring MVC), ist aber in erster Linie für spezielle Umstände vorgesehen, die im Zusammenhang mit Portlet-Anwendungen auftreten können.

Anhang C: „Spring XML Configuration Reference“ (Referenz zur Spring-XML-Kon- figuration). Dieser Anhang dokumentiert alle XML-Konfigurationselemente, die in Spring 2.0 vorhanden sind. Außerdem enthält er die Konfigurationselemente für Spring Web Flow und Direct Web Remoting (DWR).

Anhang D: „Spring JSP Tag Library Reference“ (Referenz zur Spring JSP Tag-Biblio-thek). Dieser Anhang dokumentiert alle JSP-Tags, d. h. sowohl die ursprünglichen Spring JSP-Tags als auch die neuen formularbindenden Tags aus Spring 2.0.

Anhang E: „Spring Web Flow Definition Reference“ (Referenz zu Spring Web Flow). Dieser Anhang listet alle XML-Elemente auf, die zur Definition eines Ablaufs für Spring Web Flow erforderlich sind.

Anhang F: „Customizing Spring Configuration“ (Spring-Konfiguration anpassen). Dieser Anhang, der ursprünglich Bestandteil von Kapitel 3 war, zeigt Ihnen, wie Sie benutzerdefinierte Namespaces für die Spring-XML-Konfiguration erstellen.

Diese Kapitel und Anhänge enthalten einige interessante Informationen – ich wollte sie nicht umsonst geschrieben haben. Also überzeugte ich Manning davon, sie zum kosten-losen Download freizugeben. Sie können dieses Bonusmaterial online herunterladen unter http://www.manning.com/SpringinAction.

An wen richtet sich dieses Buch ? Spring im Einsatz wurde für alle Java-Entwickler geschrieben, doch insbesondere Ent-wickler in Unternehmensumgebungen werden es sehr nützlich finden. Ich zeige Ihnen in den einzelnen Kapiteln zwar Codebeispiele, die an Komplexität stetig zunehmen, doch die wirkliche Stärke von Spring besteht in der Fähigkeit, die Entwicklung von Unternehmens-anwendungen zu vereinfachen. Insofern werden insbesondere Unternehmensentwickler die vorgestellten Beispiele zu schätzen wissen. Da ein Großteil von Spring der Bereitstellung von Unternehmensdiensten gewidmet ist, lassen sich viele Parallelen zwischen Spring und EJB ziehen. Deswegen ist jedes bei Ihnen vorhandene Wissen nützlich, um Vergleiche zwischen diesen beiden Frameworks ziehen zu können. Zwar legt unser Buch den Schwerpunkt nicht ausschließlich auf Webanwendungen, wid-met sich diesem Thema aber in großem Umfang. Insbesondere die letzten vier Kapitel ver-anschaulichen, wie Spring die Entwicklung der Webschicht Ihrer Anwendungen unter-stützt. Wenn Sie Webanwendungen entwickeln, werden Sie diesen letzten Teil des Buchs besonders praktisch finden.

Page 12: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

Zu diesem Buch

XXV

Codespezifische Konventionen In diesem Buch finden Sie zahlreiche Codebeispiele. Die Beispiele erscheinen stets in ei-ner Festbreitenschrift. Wenn im Code Teile vorhanden sind, auf die wir Ihr besonde-res Augenmerk richten wollen, erscheinen diese in fetter Festbreitenschrift. Auch Klassennamen, Methodennamen oder XML-Fragmente im Fließtext werden in der Fest-breitenschrift aufgeführt. Viele der Klassen und Packages in Spring tragen aussagekräftige Namen, die jedoch außergewöhnlich lang sein können. Aus diesem Grund werden bei Bedarf Ellipsen (…) ein-geführt, die auf gekürzte Namen hinweisen. Nicht alle Codebeispiele in diesem Buch sind vollständig. Häufig zeigen wir nur eine oder zwei Methoden einer Klasse, um den Schwerpunkt auf ein bestimmtes Thema zu legen. Den vollständigen Quellcode der im Buch verwendeten Beispielanwendung können Sie auf www.manning.com/walls3 oder www.manning.com/SpringinAction herunterladen.

Der Autor Craig Walls ist seit mehr als 13 Jahren als Softwareentwickler tätig und war Koautor von XDoclet in Action (Manning, 2003). Er ist eifriger Verfechter des Spring-Frameworks, hält häufig Vorträge bei Benutzergruppen und auf Konferenzen und schreibt auch in seinem Blog über Spring. Sofern er nicht gerade neuen Code strickt, verbringt er möglichst viel Zeit mit seiner Frau, seinen beiden Töchtern, sechs Vögeln, vier Hunden, zwei Katzen und einer ständig schwankenden Anzahl tropischer Zierfische. Craig lebt in Denton, Texas.

Author Online Der Erwerb von Spring im Einsatz berechtigt zum kostenfreien Zugriff auf das private Webforum Author Online von Manning Publications, wo Sie Kommentare zum Buch abgeben, technische Fragen stellen und Hilfe von den Autoren und anderen Benutzern er-halten können. Um auf das Forum zugreifen und es abonnieren zu können, rufen Sie die Seiten www.manning.com/walls3 oder www.manning.com/SpringinAction auf. Diese Seite enthält Informationen dazu, wie man nach der Registrierung in das Forum gelangt, welche Hilfe verfügbar ist und welche Verhaltensregeln im Forum gelten. Absicht von Manning ist es, seinen Lesern einen Ort zu bieten, an dem ein sinnvoller Dia-log unter einzelnen Lesern sowie zwischen den Lesern und dem Autor stattfinden kann. Die Teilnahme des Autors bei diesem Forum erfolgt freiwillig (und wird auch nicht vergü-tet). Wir möchten Sie bitten, dem Autor durchaus anspruchsvolle Fragen zu stellen, da sein Interesse andernfalls erlahmen könnte! Das Forum Author Online und die Archive früherer Diskussionen können über die Website aufgerufen werden, solange das Buch angeboten wird.

Page 13: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

Craig Walls, Ryan Breidenbach

Spring im EinsatzÜbersetzt aus dem Englischen von Christian Alkemper, Jürgen

Dubau

ISBN-10: 3-446-41240-9ISBN-13: 978-3-446-41240-8

Leseprobe

Weitere Informationen oder Bestellungen unterhttp://www.hanser.de/978-3-446-41240-8

sowie im Buchhandel

Page 14: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

225

7 7 Spring absichern

Dieses Kapitel behandelt die folgenden Themen: Einführung in Spring Security Absichern von Webanwendungen mit Servlet-Filtern Authentifizierung gegenüber Datenbanken und LDAP Transparentes Schützen von Methodenaufrufen

Ist Ihnen eigentlich schon einmal aufgefallen, dass die Leute in amerikanischen Fernsehse-rien niemals ihre Türen abschließen? Das kommt ständig vor. In Seinfeld verschafft sich Kramer häufig Einlass in Jerrys Apartment, um sich an den Leckerbissen aus Jerrys Kühl-schrank zu vergreifen. In Friends gehen die verschiedenen Mitwirkenden in den Wohnun-gen anderer Charaktere ein und aus – ohne Zögern und ohne Vorwarnung. Einmal platzt Ross sogar in Chandlers Londoner Hotelzimmer – und verpasst Chandler nur um Haares-breite in einer verfänglichen Situation mit seiner – Ross’ – Schwester. In den Fünfziger- und frühen Sechzigerjahren war es in den Vereinigten Staaten noch nicht so ungewöhnlich, Wohnungstüren unverschlossen zu lassen. Heute allerdings erscheint es uns verrückt, wenn Personen im Fernsehen ungehindert Zugang zu fremden Wohnungen und Häusern erhalten, während alle Welt von Privatsphäre und Sicherheit redet. Es ist eine traurige Wirklichkeit, dass schurkische Elemente stets danach trachten, uns um Geld, Wertsachen, Autos und andere Besitztümer zu erleichtern. Weil Daten zum Wert-vollsten gehören, was wir besitzen, ist es auch keine Überraschung, dass Halunken nach Möglichkeiten suchen, unsere Daten und/oder unsere Identität zu stehlen, indem sie in un-gesicherten Anwendungen herumschnüffeln. Als Softwareentwickler müssen wir Maßnahmen ergreifen, um die Daten zu schützen, die sich in unseren Anwendungen befinden. Egal, ob es sich um ein mit Benutzername und Passwort geschütztes Mailkonto oder ein Aktienkonto handelt, das per PIN abgesichert ist: Sicherheit ist ein wesentlicher Aspekt der meisten Anwendungen.

Page 15: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

226

Es ist kein Zufall, dass ich die Anwendungssicherheit mit dem Wort Aspekt beschreibe. Sicherheit ist ein Faktor, der die Funktionalität einer Anwendung übersteigt. In aller Regel sollte die Anwendung selbst keine Rolle bei ihrer Absicherung spielen. Zwar könnten Sie Sicherheitsfunktionen auch direkt in den Anwendungscode schreiben (was durchaus vor-kommt), doch ist es besser, sicherheitstechnische Maßnahmen von der Anwendung zu trennen. Sie meinen, das klingt so, als ob sich Sicherheit mithilfe aspektorientierter Techniken rea-lisieren ließe? Nun, Sie haben recht. In diesem Kapitel untersuchen wir Möglichkeiten, Ihre Anwendungen mit Aspekten zu schützen. Wir müssen diese Aspekte allerdings nicht selbst entwickeln, sondern benutzen einfach Spring Security, ein auf Spring AOP und Servlet-Filtern basierendes Sicherheits-Framework.1

7.1 Einführung in Spring Security

Spring Security ist ein Sicherheits-Framework, welches deklarative Sicherheit für Ihre Spring-basierten Anwendungen bietet. Spring Security stellt eine umfassende Sicherheits-lösung dar, die Authentifizierung und Autorisierung auf Webanfrage- und Methodenauf-rufebene gleichermaßen behandelt. Es basiert auf dem Spring-Framework und nutzt des-wegen gewissenhaft Dependency Injection (DI) und aspektorientierte Techniken.

Namen sind Schall und Rauch Früher hieß Spring Security einmal Acegi Security (oder einfach Acegi). Acegi ist seit langer Zeit ein Unterprojekt von Spring. Während ich dies schreibe, werden jedoch Pläne geschmiedet, Acegi noch stärker mit den Spring-Projekten zu verzahnen. Eine Maßnahme im Zuge dieses Ansatzes ist das Fallenlassen des Namens Acegi zuguns-ten von Spring Security. Diese Änderung soll beginnend mit Version 1.1.0 von Acegi/ Spring Security durchgeführt werden. Im Wissen, dass diese Änderung direkt bevor-steht, habe ich beschlossen, ein wenig vorzugreifen und vorzugsweise die Bezeich-nung Spring Security zu verwenden, auch wenn der Begriff Acegi in diesem Kapitel hier und da noch auftauchen wird.

Beim Absichern von Webanwendungen verwendet Spring Security Servlet-Filter, die Servlet-Anfragen abfangen, um Authentifizierungsschritte durchzuführen und die Sicherheit durchzusetzen. Wie Sie außerdem in Abschnitt 7.4.1 herausfinden werden, setzt Spring Security einen einzigartigen Mechanismus zur Deklaration von Servlet-Filtern ein, der es Ihnen ermöglicht, diesen Filtern mithilfe der DI Abhängigkeiten zu injizieren.

1 Ich werde wegen dieses Ausspruchs wahrscheinlich eine Menge E-Mails bekommen, aber ich komme

trotzdem nicht umhin, es so zu formulieren: Servlet-Filter stellen eine primitive Form von AOP dar, bei denen URL-Patterns als eine Art Pointcut-Ausdruckssprache dienen. So, nun ist es heraus. Jetzt geht es mir besser.

Page 16: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.1 Einführung in Spring Security

227

Spring Security kann Sicherheit durch Absicherung von Methodenaufrufen auch auf einer niedrigeren Ebene durchsetzen. Beim Absichern von Methoden erstellt Spring Security mithilfe von Spring AOP Proxys von Objekten und wendet dabei Aspekte an, die sicherstellen, dass der Benutzer über die erforderlichen Rechte verfügt, um die geschützten Methoden aufzurufen.

In jedem Fall – ob zur Absicherung nur auf der Ebene der Webanfragen oder auch auf der niedrigeren Methodenebene – verwendet Spring Security fünf Kernkomponen-ten, die in Abbildung 7.1 gezeigt werden.

Sicherheits-

Interceptor

Authentication-

Manager

Access-Decision-

Manager

Run-As-

Manager

After-Invocation-

Manager

Abbildung 7.1 Basiselemente von Spring Security

Bevor wir nun zur Sache kommen, wollen wir uns zunächst einen allgemeinen Überblick zu Spring Security und der Rolle verschaffen, die die einzelnen Komponenten bei der Ab-sicherung von Anwendungen spielen.

Sicherheits-Interceptors Wenn Sie nach einem langen Tag nach Hause kommen, müssen Sie zunächst die Tür zu Ihrer Wohnung aufsperren. Zu diesem Zweck führen Sie in das Schloss einen passenden Schlüssel ein, der in die Stifte im Schlossinnern eingreift und den Riegel freigibt. Wenn der Schlüssel nicht passt, werden die Stifte nicht ausgelöst, und der Riegel kann nicht frei-gegeben werden. Verfügen Sie jedoch über den richtigen Schlüssel, dann werden alle Schlossstifte in ihre jeweilig korrekte Position gesetzt und der Riegel ausgeschoben: Die Tür ist nun offen. In Spring Security kann man sich den Sicherheits-Interceptor als Riegel vorstellen, der verhindern soll, dass Sie auf eine abgesicherte Ressource in Ihrer Anwendung zugreifen. Um den Riegel zu öffnen und am Sicherheits-Interceptor vorbei zu kommen, müssen Sie Ihren „Schlüssel“ – meist in Form einer Kombination aus Benutzername und Passwort – in das System eingeben. Der Schlüssel verschiebt dann die „Stifte“ des Sicherheits-Inter-ceptors und versucht, Ihnen auf diese Weise Zugang zu der gewünschten Ressourcen zu verschaffen. Die eigentliche Implementierung eines Sicherheits-Interceptors hängt von der abzusichern-den Ressource ab. Wenn Sie einen URL in einer Webanwendung schützen, wird der Sicherheits-Interceptor als Servlet-Filter implementiert. Sichern Sie hingegen einen Me-thodenaufruf ab, dann wird die Sicherheit mithilfe von Aspekten erzielt. Beide Ausprä-gungen des Sicherheits-Interceptors beschreiben wir im Verlauf dieses Kapitels.

Page 17: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

228

Ein Sicherheits-Interceptor tut wenig mehr, als Zugriffe auf Ressourcen abzufangen, um die Sicherheit zu erzwingen. Eigentlich wendet er überhaupt keine Sicherheitsregeln an; stattdessen delegiert er diese Aufgabe an diverse Manager, die Abbildung 7.1 zeigt. Wir wollen uns diese Manager einmal ansehen und beginnen mit dem Authentication-Manager.

Authentication-Manager Der erste Sicherheitsstift des Sicherheits-Interceptors, der einrasten muss, ist der Authenti-cation-Manager. Der Authentication-Manager soll ermitteln, wer Sie sind. Dies tut er in der Regel, indem er Ihren Prinzipal (meist ein Benutzername) und Ihre Anmeldeinforma-tionen (engl. Credentials, also Ihr Passwort) kontrolliert. Ihr Prinzipal definiert, wer Sie sind, und Ihre Anmeldeinformationen dienen als Nachweis für Ihre Identität. Wenn Ihre Anmeldeinformationen ausreichen, den Authentication-Mana-ger davon zu überzeugen, dass Ihr Prinzipal Sie ausweist, weiß Spring Security, mit wem sie es nun zu tun hat. Wie der Rest von Spring Security (und Spring selbst) ist der Authentication-Manager eine einschleifbare Interface-basierte Komponente. Aus diesem Grund können wir Spring Secu-rity mit praktisch jedem vorstellbaren Authentifizierungsmechanismus verwenden. Wie Sie im weiteren Verlauf noch sehen werden, enthält Spring Security eine Handvoll flexib-ler Authentication-Manager, die die gängigsten Authentifizierungsstrategien abdecken.

Access-Decision-Manager Nachdem Spring Security ermittelt hat, wer Sie sind, muss entschieden werden, ob Sie au-torisiert sind, auf die geschützte Ressource zuzugreifen. Ein Access-Decision-Manager ist der zweite Stift, der einrasten muss. Der Access-Decision-Manager führt die Autorisierung durch und entscheidet, ob Sie eingelassen werden. Hierzu kontrolliert er Ihre Authentifi-zierungsangaben und die Sicherheitsattribute, die mit der geschützten Ressource verknüpft sind. So können die Sicherheitsregeln beispielsweise vorschreiben, dass nur Prüfer auf eine geschützte Ressource zugreifen dürfen. Wenn Ihnen Prüferberechtigungen gewährt wur-den, rastet der zweite und letzte Schlossstift – der Access-Decision-Manager – ein, der Sicherheits-Interceptor springt aus dem Weg. Nun ist der Weg zur geschützten Ressource frei. Wie beim Authentication-Manager ist auch der Access-Decision-Manager einschleifbar. Im weiteren Verlauf dieses Kapitels werden wir uns die mit Spring Security mitgelieferten Access-Decision-Manager genauer ansehen.

Run-As-Manager Sind Sie an Authentication-Manager und Access-Decision-Manager vorbei, dann wird der Sicherheits-Interceptor entsperrt, und die Tür kann geöffnet werden. Bevor Sie jedoch die Klinke drücken und eintreten, könnte der Sicherheits-Interceptor erneut aktiv werden.

Page 18: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.1 Einführung in Spring Security

229

Sie haben zwar die Authentifizierung hinter sich gebracht und Zugriff auf eine Ressource erhalten, aber hinter der Tür lauern womöglich weitere Sicherheitseinrichtungen. So kön-nen Sie beispielsweise das Recht erhalten haben, eine Webseite anzuzeigen, während für die Objekte, mit denen diese Seite erstellt wird, andere Sicherheitsanforderungen gelten. Ein Run-As-Manager kann verwendet werden, um Ihre Authentifizierung durch eine ande-re zu ersetzen, die Ihnen den Zugriff auf die geschützten Objekte gestattet, die sich auf anderen Ebenen Ihrer Anwendung befinden. Beachten Sie, dass eine solche Identitätsersetzung nicht bei allen Anwendungen erforder-lich ist. Aus diesem Grund sind Run-As-Manager eine optionale Sicherheitskomponente und bei vielen mit Spring Security geschützten Anwendungen nicht erforderlich.

After-Invocation-Manager Der After-Invocation-Manager von Spring Security unterscheidet sich ein wenig von den anderen sicherheitsverwaltenden Komponenten. Während die übrigen Sicherheitsmanager-komponenten eine Form der Sicherheitserzwingung jeweils vor dem Zugriff auf die ge-schützte realisieren, tut der After-Invocation-Manager dies danach. After-Invocation-Manager ähneln dem Sicherheitspersonal, das am Ausgang eines Super- oder Elektronikfachmarkts Ihre Quittung sehen will. Ihre Aufgabe besteht darin, zu über-prüfen, ob Sie das Recht haben, die mitgeführten Artikel aus dem Laden zu transportieren. After-Invocation-Manager überprüfen nicht, ob Sie den 24-Zoll-Plasmabildschirm mit-nehmen dürfen, sondern stellen sicher, dass Sie die Daten anzeigen dürfen, die von einer geschützten Ressource an Sie zurückgegeben wurden. Wenn ein After-Invocation-Manager eine Serviceschicht-Bean mit einem Advice ver-knüpft, erhält er die Möglichkeit, den von dieser Methode zurückgegebenen Wert zu über-prüfen. Danach kann er entscheiden, ob der Benutzer diesen Rückgabewert anzeigen darf oder nicht. Der After-Invocation-Manager ist zudem in der Lage, den Rückgabewert zu ändern, um sicherzustellen, dass der Benutzer nur auf bestimmte Eigenschaften des Rück-gabeobjekts zugreifen kann. Wie beim Run-As-Manager benötigen auch nicht alle Anwendungen einen After-Invo-cation-Manager. Einen solchen brauchen Sie nur, wenn das Sicherheitsschema Ihrer An-wendung es erfordert, dass der Zugriff auf der Domänenebene instanzenspezifisch einge-schränkt wird. Nachdem wir nun das Gesamtbild von Spring Security gesehen haben, können wir Spring Security für die Anwendung RoadRantz konfigurieren. Für unsere Zwecke benötigen wir keinen Run-As-Manager oder After-Invocation-Manager, weshalb wir diese als fortge-schrittene Spring Security-Themen zunächst zurückstellen wollen. Beginnen wir also mit der Konfiguration eines Authentication-Managers.

Page 19: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

230

7.2 Benutzer authentifizieren

Wenn Sie Sicherheitseinstellungen für eine Anwendung konfigurieren, müssen Sie, bevor Sie entscheiden, ob Zugriff gewährt werden soll, erst einmal herausfinden, wer der Benut-zer ist. Bei den meisten Anwendungen bedeutet dies, einen Anmeldedialog anzuzeigen und den Benutzer zur Eingabe von Benutzername und Passwort aufzufordern. Wie genau der Benutzer zur Eingabe der Anmeldeinformationen aufgefordert wird, variiert von Anwendung zu Anwendung. An dieser Stelle wollen wir annehmen, dass die Anmel-deinformationen bereits durch den Benutzer eingegeben wurden und Spring Security die-sen nun authentifizieren muss. Die unterschiedlichen Möglichkeiten zur Anforderung von Benutzername und Passwort behandeln wir in diesem Kapitel weiter unten. In Spring Security übernimmt der Authentication-Manager die Aufgabe, die Identität des Benutzers zu verifizieren. Ein Authentication-Manager wird über das Interface org.acegi-security.AuthenticationManager definiert:

public interface AuthenticationManager { public Authentication authenticate(Authentication authentication) throws AuthenticationException; }

Aller guten Dinge sind drei Ha! Im Package-Namen von AuthenticationManager findet sich das Wörtchen Acegi. Wie bereits erwähnt, ist dies der alte Name von Spring Security. Nach der formellen Umbenennung von Acegi in Spring Security wird sich dies auch in den Klassen und Packages niederschlagen. Eigentlich ist das schon der dritte Package-Basisname von Acegi/Spring Security: Ursprünglich war Acegi in net.sf.acegi-security gepackt, später dann in org.acegisecurity. Nach der Freigabe von Version 1.1.0 wird es wahrscheinlich in org.springframework.security neu ge-packt. Nichtsdestoweniger enthalten die Beispiele in diesem Kapitel das Package org.acegisecurity, weil diese Änderungen noch nicht stattgefunden haben.

Die Methode authenticate() wird versuchen, den Benutzer mit dem Objekt org.acegisecurity.Authentication zu authentifizieren (dieses Objekt enthält den Prinzipal und die Anmeldeinformationen). Sofern erfolgreich, gibt die Methode ein voll-ständiges Authentication-Objekt zurück, welches Informationen zu den dem Benutzer gewährten Berechtigungen enthält (diese werden dann vom Authentication-Manager über-prüft). Schlägt die Authentifizierung fehl, dann wird eine AuthenticationException ausgelöst. Wie Sie sehen, ist das Interface AuthenticationManager recht schlicht gehalten – Sie könnten problemlos einen eigenen AuthenticationManager implementieren. Spring Security enthält allerdings ProviderManager, eine Implementierung von Authenticati-onManager, die für die meisten Situationen durchaus geeignet ist. Anstatt nun also einen eigenen Authentication-Manager zu konstruieren, wollen wir einmal sehen, wie man Pro-viderManager verwendet.

Page 20: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.2 Benutzer authentifizieren

231

7.2.1 Einen Provider-Manager konfigurieren

ProviderManager ist eine Implementierung eines Authentication-Managers, die die Zu-ständigkeit für die Authentifizierung an einen oder mehrere Authentifizierungsanbieter delegiert (vgl. Abbildung 7.2).

Provider

Manager

Dao

Authentication

Provider

Jaas

Authentication

Provider

Ldap

Authentication

Provider

Cas

Authentication

Provider

Remote

Authentication

Provider

Authentication-Manager

Abbildung 7.2 Ein ProviderManager delegiert die Zuständigkeit für die Authentifizierung an einen oder mehrere Authentifizierungsanbieter.

Der Zweck von ProviderManager besteht darin, Benutzer gegenüber mehreren Quellen zur Identitätsverwaltung zu authentifizieren. Statt sich zur Authentifizierung auf sich selbst zu verlassen, arbeitet ProviderManager Schritt für Schritt eine Sammlung von Authenti-fizierungsanbietern ab, bis einer von ihnen den Benutzer erfolgreich authentifiziert (oder kein weiterer Anbieter mehr vorhanden ist). Dies ermöglicht es Spring Security, mehrere Authentifizierungsmechanismen für eine einzelne Anwendung zu unterstützen. Der folgende XML-Ausschnitt zeigt eine typische Konfiguration von ProviderManager in der Spring-Konfigurationsdatei:

<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager"> <property name="providers"> <list> <ref bean="daoAuthenticationProvider"/> <ref bean="ldapAuthenticationProvider"/> </list> </property> </bean>

ProviderManager erhält eine Liste der Authentifizierungsanbieter über seine Eigenschaft providers. Gewöhnlich werden Sie nur einen Authentifizierungsanbieter benötigen, aber in manchen Fällen kann es sinnvoll sein, eine Liste mit mehreren Anbietern anzugeben, sodass, wenn die Authentifizierung bei einem Anbieter fehlschlägt, ein anderer ausprobiert werden kann. Spring bringt eine Reihe von Authentifizierungsanbietern mit (siehe Tabelle 7.1).

Page 21: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

232

Tabelle 7.1 Spring Security bietet Authentifizierungsanbieter für jede Gelegenheit.

Authentifizierungsanbieter (org.acegisecurity.*)

Zweck

adapters.AuthByAdapterProvider Authentifizierung mithilfe von Containeradaptern. Diese ermöglichen eine Authentifizierung gegen-über Benutzern, die im Webcontainer erstellt wurden (z. B. Tomcat, JBoss, Jetty, Resin usw.).

providers.anonymous. AnonymousAuthenticationProvider

Authentifiziert einen Benutzer als anonymen Be-nutzer. Dies ist nützlich, wenn ein Benutzer-Token erforderlich ist, auch wenn der Benutzer sich noch nicht angemeldet hat.

providers.cas. CasAuthenticationProvider

Authentifizierung gegenüber dem JA-SIG CAS (Central Authentication Service). Nützlich, wenn Sie Einzelanmeldungsfunktionalitäten benötigen.

providers.dao. DaoAuthenticationProvider

Abrufen von Benutzerdaten einschließlich Benut-zername und Passwort aus einer Datenbank

providers.dao. LdapAuthenticationProvider

Authentifizierung gegenüber einem LDAP-Server (Lightweight Directory Access Protocol)

providers.jaas. JaasAuthenticationProvider

Abrufen von Benutzerdaten aus einer JAAS-Anmeldekonfiguration

providers.rememberme.RememberMe-AuthenticationProvider

Authentifiziert einen Benutzer, der bereits zuvor authentifiziert und gespeichert wurde. Ermöglicht die automatische Anmeldung eines Benutzers ohne Abfrage von Benutzername und Passwort.

providers.rcp. RemoteAuthenticationProvider

Authentifizierung gegenüber einem Remote-Service.

providers.Testing-AuthenticationProvider

Unit-Test. Betrachtet ein TestingAuthentica-tionToken automatisch als gültig. Nicht für den Einsatz in Produktionsumgebungen vorgesehen.

providers.x509. X509AuthenticationProvider

Authentifizierung mithilfe eines X.509-Zertifikats. Nützlich zur Authentifizierung von Benutzern, die eigentlich andere Anwendungen sind (z. B. ein Webserviceclient).

runas. RunAsImplAuthenticationProvider

Authentifizierung eines Benutzers, dessen Identität durch einen Run-As-Manager ersetzt wurde.

Wie Sie Tabelle 7.1 entnehmen können, bietet Spring Security einen Authentifizierungs-anbieter für fast jede Anforderung. Wenn Sie allerdings trotzdem keinen Authentifizie-rungsanbieter finden, der den Sicherheitsansprüchen Ihrer Anwendung genügt, können Sie stets einen eigenen Anbieter erstellen, indem Sie das Interface org.acegisecurity. providers.AuthenticationProvider implementieren:

public interface AuthenticationProvider { Authentication authenticate(Authentication authentication) throws AuthenticationException; boolean supports(Class authentication); }

Page 22: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.2 Benutzer authentifizieren

233

Sie haben vielleicht schon bemerkt, dass sich das Interface AuthenticationProvider nicht besonders vom einige Seiten früher präsentierten Interface AuthenticationMana-ger unterscheidet. Beide enthalten eine Methode authenticate(), die die Authentifizie-rung behandelt. Und tatsächlich können Sie sich Authentifizierungsanbieter durchaus als nachrangige Authentication-Manager vorstellen. Der Rahmen dieses Buches gestattet es mir nicht, alle elf Authentifizierungsanbieter aus Spring Security zu beschreiben. Ich will jedoch die meistverwendeten Anbieter herausstel-len und detailliert beschreiben. Beginnen will ich dabei mit DaoAuthenticationPro-vider, der die einfache datenbankorientierte Authentifizierung unterstützt.

7.2.2 Authentifizierung gegenüber einer Datenbank

Viele Anwendungen speichern Benutzerdaten wie Benutzername oder Passwort in einer relationalen Datenbank. Wenn Ihre Anwendung diese Art der Benutzerdatenhaltung ver-folgt, ist Spring Securitys DaoAuthenticationProvider unter Umständen eine gute Wahl. Ein DaoAuthenticationProvider ist ein einfacher Authentifizierungsanbieter, der mit-hilfe eines DAOs Benutzerdaten (wie etwa das Passwort) aus einer relationalen Datenbank abruft. Hat er diese Angaben erhalten, so führt DaoAuthenticationProvider die Authentifizie-rung durch, indem er Benutzername und Passwort, die aus der Datenbank abgerufen wur-den, mit dem Prinzipal und den Anmeldeinformationen vergleicht, die vom Authentica-tion-Manager in einem Authentication-Objekt übergeben wurden (siehe Abbildung 7.3). Entsprechen Benutzername und Passwort dem Prinzipal bzw. den Anmeldeinformationen, dann wird der Benutzer authentifiziert und ein vollständig ausgefülltes Authentication-Objekt an den Authentication-Manager zurückgegeben. Andernfalls wird eine Authenti-cationException ausgelöst, und die Authentifizierung ist fehlgeschlagen.

Authentication-

Manager

(ProviderManager)

Dao

Authentication

Provider

Benutzer-

detailservice

authenticate()

loadUserByUsername()

Benutzer-

datenbank

Abbildung 7.3 Ein DaoAuthentication-Manager authentifiziert Benutzer im Auftrag des Authentication-Manager, indem er die Benutzer- angaben aus einer Datenbank abruft.

Das Konfigurieren eines DaoAuthenticationProviders könnte einfacher nicht sein. Der folgende XML-Codeausschnitt zeigt, wie man eine DaoAuthenticationProvider-Bean erstellt und mit einer Referenz auf ihr DAO verschaltet:

<bean id="authenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="userDetailsService"/> </bean>

Page 23: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

234

Die Eigenschaft userDetailsService wird verwendet, um die Bean zu ermitteln, mit der die Benutzerdaten aus der Datenbank abgerufen werden. Diese Eigenschaft erwartet eine Instanz von org.acegisecurity.userdetails.UserDetailsService. Die Frage, die bleibt, lautet, wie die userDetailsService-Bean konfiguriert wird. Das Interface UserDetailsService erfordert die Implementierung genau einer Methode.

public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException; }

Diese Methode ist weitgehend selbsterklärend, und Ihnen fallen bestimmt spontan einige Möglichkeiten ein, dieses Interface zu implementieren. Doch bevor Sie damit beginnen, eigene Implementierungen von UserDetailsService zu schreiben, interessiert es Sie vielleicht, dass Spring Security bereits zwei gebrauchsfertige Implementierungen von AuthenticationDao enthält: InMemoryDaoImpl und JdbcDaoImpl. Wir wollen einmal sehen, wie diese beiden Klassen Benutzerinformationen nachschlagen. Beginnen werden wir mit InMemoryDaoImpl.

Speicherresidente DAOs verwenden Zwar scheint es naheliegend, anzunehmen, dass ein AuthenticationDao-Objekt stets eine relationale Datenbank nach Benutzerinformationen abfragt, doch muss dies nicht unbe-dingt der Fall sein. Wenn die Authentifizierungsbedürfnisse Ihrer Anwendung trivial sind oder es lediglich um die Bequemlichkeit während der Entwicklung geht, kann es einfacher sein, Ihre Benutzerdaten direkt in der Spring-Konfigurationsdatei zu konfigurieren. Zu diesem Zweck bietet Spring Security mit InMemoryDaoImpl eine Implementierung von UserDetailsService, die die Benutzerangaben der Spring-Konfiguration entnimmt. Hier ein Beispiel dafür, wie man InMemoryDaoImpl in der Spring-Konfigurationsdatei konfigu-rieren könnte:

<bean id="authenticationDao" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl"> <property name="userMap"> <value> palmerd=4moreyears,disabled,ROLE_PRESIDENT bauerj=ineedsleep,ROLE_FIELD_OPS obrianc=nosmile,ROLE_SR_ANALYST,ROLE_OPS myersn=traitor,disabled,ROLE_CENTRAL_OPS </value> </property> </bean>

Die Eigenschaft userMap wird mit einem org.acegisecurity.userdetails.memory. UserMap-Objekt konfiguriert, das einen Satz von Benutzernamen, Passwörtern und Be-rechtigungen definiert. Zum Glück müssen Sie sich nicht mit der Erstellung einer User-Map-Instanz befassen, wenn Sie InMemoryDaoImpl verschalten, denn es gibt einen Eigen-schaftseditor, der die Konvertierung eines String- in ein UserMap-Objekt für Sie über-nimmt. In jeder Zeile der userMap ist String ein Name-Wert-Paar, wobei als Name der Benut-zername angegeben wird, während der Wert eine kommagetrennte Liste enthält, die das Be-

Page 24: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.2 Benutzer authentifizieren

235

myersn=traitor,disabled,ROLE_CENTRAL_OPS

Benutzername

Passwort Berechtigungen

Aktivierungsstatus (optional)

Abbildung 7.4 userMap aus Spring Security, die einen Benutzernamen mit einem Passwort, gewährten Berechtigungen und optional einem Status verknüpft

nutzerpasswort sowie nachfolgend die Berechtigungen angibt, die dem Benutzer gewährt werden. Abbildung 7.4 entschlüsselt das Format eines Eintrags in der userMap. In der Deklaration der authenticationDao-Bean weiter oben haben wir vier Benutzer definiert: parlmerd, bauerj, obrianc und myersn. Deren Passwörter lauten 4moreyears, ineedsleep, nosmile und traitor. Die folgenden Berechtigungen wurden konfiguriert:

Die Berechtigung ROLE_PRESIDENT wurde dem Benutzer mit dem Benutzernamen palmerd gewährt.

ROLE_FIELD_OPS wurde für bauerj konfiguriert.

ROLE_CENTRAL_OPS wurde für myersn konfiguriert.

Dem Benutzer obrianc wurden zwei Rollen zugewiesen: ROLE_SR_ANALYST und ROLE_OPS.

Beachten Sie insbesondere die Benutzer palmerd und myersn. Den Passwörtern folgt das Spezial-Flag disabled, d. h. diese Benutzer wurden deaktiviert und können sich infolge-dessen auch nicht authentifizieren. InMemoryDaoImpl ist zwar einfach und bequem, unterliegt aber auch einigen offensichtli-chen Einschränkungen. In erster Linie erfordert die Sicherheitsadministration, dass Sie die Spring-Konfigurationsdatei bearbeiten und Ihre Anwendung dann neu deployen. Dies ist zwar zulässig (und kann sogar hilfreich sein), wenn man in einer Entwicklungsumgebung arbeitet, aber in der Produktion ist eine solche Vorgehensweise wahrscheinlich zu unflexi-bel. Aus diesem Grund rate ich vom Einsatz von InMemoryDaoImpl in einer Produktions-umgebung dringend ab. Stattdessen sollten Sie die Verwendung von JdbcDaoImpl in Be-tracht ziehen, der wir uns nun zuwenden wollen.

JDBC-DAOs deklarieren JdbcDaoImpl ist ein einfaches, aber flexibles Authentifizierungs-DAO, das Benutzerdaten aus einer relationalen Datenbank abruft. In seiner einfachsten Form benötigt es lediglich eine Referenz auf eine javax.sql.DataSource und kann in der Spring-Konfigurations-datei wie folgt deklariert werden:

<bean id="authenticationDao" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean>

So, wie es hier konfiguriert ist, trifft JdbcDaoImpl einige grundsätzliche Annahmen da-rüber, wie die Benutzerdaten in der Datenbank gespeichert sind. Insbesondere werden eine Tabelle users und eine Tabelle authorities erwartet (siehe Abbildung 7.5).

Page 25: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

236

Users

username : String

password : String

enabled : boolean

Authorities

username : String

authority : String

Abbildung 7.5 Von JdbcDaoImpl erwartete Datenbanktabellen

Wenn JdbcDaoImpl Benutzerangaben nachschlägt, erfolgt die Abfrage mit dem folgenden SQL-Code:

SELECT username, password, enabled FROM users WHERE username = ?

Ähnlich verwendet JdbcDaoImpl den folgenden Code, wenn Sie die einem Benutzer ge-währten Berechtigungen nachschlagen:

SELECT username, authority FROM authorities WHERE username = ?

Zwar sind die von JdbcDaoImpl erwarteten Tabellenstrukturen sehr einfach, doch werden sie den Tabellen, die Sie selbst für die Absicherung Ihrer Anwendung eingerichtet haben, wahrscheinlich nicht entsprechen. In der Anwendung RoadRantz beispielsweise enthält die Tabelle Motorist die Benutzernamen registrierter Benutzer (in der Spalte mit den Mailadressen) sowie das Passwort. Bedeutet dies, dass wir Fahrer in der Anwendung RoadRantz nicht mit JdbcDaoImpl authentifizieren können? Keineswegs. Doch wenn wir JdbcDaoImpl wollen, müssen wir ein wenig nachhelfen, da-mit die Benutzerangaben gefunden werden. Hierzu verwenden wir die Eigenschaft users-ByUsernameQuery. Die folgende Änderung an der Bean authenticationDao konfiguriert diese so, dass Benutzer aus der RoadRantz-Tabelle Motorist abgefragt werden:

<bean id="authenticationDao" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl"> <property name="dataSource" ref bean="dataSource" /> <property name="usersByUsernameQuery"> <value> SELECT email as username, password, enabled FROM Motorist WHERE email=? </value> </property> </bean>

Nun weiß JdbcDaoImpl, dass die Authentifizierungsdaten in der Tabelle Motorist vor-handen sind. Allerdings müssen wir JdbcDaoImpl auch mitteilen, wie die einem Benutzer gewährten Berechtigungen der Datenbank entnommen werden können. Hierfür kommt die Eigenschaft authoritiesByUsernameQuery zum Einsatz:

<bean id="authenticationDao" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl"> <property name="dataSource" ref="dataSource" /> ... <property name="authoritiesByUsernameQuery"> <value> SELECT email as username, privilege as authority FROM Motorist_Privileges mp, Motorist m WHERE mp.motorist_id = m.id

Page 26: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.2 Benutzer authentifizieren

237

AND m.email=? </value> </property> </bean>

Nun haben wir JdbcDaoImpl so konfiguriert, dass die einem Fahrer gewährten Berech-tigungen aus der Tabelle Motorist_Privileges abgefragt werden. Die Abfrage bindet die Tabelle Motorist mit ein, denn die Tabelle Motorist_Privileges kennt einen Motorist nur über einen Fremdschlüssel, und JdbcDaoImpl setzt voraus, dass die Berech-tigungen über den Benutzernamen abgerufen werden.

Mit verschlüsselten Passwörtern arbeiten Wenn DaoAuthenticationProvider das (bei der Authentifizierung) vom Benutzer ange-gebene mit dem aus der Datenbank abgerufenen Passwort vergleicht, wird vorausgesetzt, dass das Passwort unverschlüsselt gespeichert wurde. Um die Sicherheit zu optimieren, sollten Sie das Passwort jedoch vor dem Speichern in der Datenbank verschlüsseln. In die-sem Fall aber muss das vom Benutzer eingegebene Passwort ebenfalls verschlüsselt wer-den, bevor die beiden Passwörter verglichen werden können. Um verschlüsselte Passwörter zu unterstützen, kann DaoAuthenticationProvider mit einem Passwortkodierer verschaltet werden. Spring Security bietet eine ganze Reihe von Passwortkodierern zur Auswahl, die in Tabelle 7.2 aufgelistet sind.

Tabelle 7.2 Passwortkodierer in Spring Security

Passwortkodierer (org.acegisecurity.providers.*)

Zweck

encoding.Md5PasswordEncoder Führt eine MD5-Verschlüsselung (Message Di-gest) des Passworts durch.

encoding.PlaintextPasswordEncoder Führt keine Verschlüsselung durch, sondern gibt das Passwort unverändert zurück.

encoding.ShaPasswordEncoder Führt eine SHA-Verschlüsselung (Secure Hash Algorithm) des Passworts durch.

ldap.authenticator. LdapShaPasswordEncoder

Führt eine LDAP SHA- und eine SSHA-Verschlüs-selung (Salted SHA) des Passworts durch.

Standardmäßig verwendet DaoAuthenticationProvider den PlaintextPassword-

Encoder, d. h. das Passwort bleibt unverschlüsselt. Wir können allerdings eine andere Ver-schlüsselung festlegen, indem wir die Eigenschaft passwordEncoder von DaoAuthenti-cationProvider verschalten. Nachfolgend ist exemplarisch gezeigt, wie man Dao-AuthenticationProvider für die MD5-Verschlüsselung verschaltet:

<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="authenticationDao" /> <property name="passwordEncoder"> <bean class="org.acegisecurity.providers.encoding.Md5PasswordEncoder" /> </property> </bean>

Page 27: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

238

Außerdem müssen Sie eine Salzquelle für den Kodierer festlegen. Eine Salzquelle stellt das Salz bereit, also den zur Verschlüsselung verwendeten Schlüssel. Spring Security unterstützt zwei Salzquellen:

SystemWideSaltSource. Stellt einen einheitlichen Schlüssel für alle Benutzer bereit. ReflectionSaltSource. Verwendet zur Schlüsselgenerierung das Reflexionsprinzip

für eine bestimmte Eigenschaft des User-Objekts. ReflectionSaltSource ist die sichere Alternative, weil das Passwort jedes Benutzers höchstwahrscheinlich mit einem anderen Salzwert verschlüsselt wird. In diesem Fall ist es auch dann, wenn ein Hacker den zur Verschlüsselung des Passworts eines Benutzers verwendeten Schlüssel ermittelt, unwahrscheinlich, dass sich mit demselben Schlüssel die Passwörter anderer Benutzer knacken lassen. Um eine ReflectionSaltSource zu ver-wenden, verschalten wir sie wie folgt in der Eigenschaft saltSource von DaoAuthenti-cationProvider:

<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="authenticationDao" /> <property name="passwordEncoder"> <bean class="org.acegisecurity.providers.encoding.Md5PasswordEncoder" /> </property> <property name="saltSource"> <bean class="org.acegisecurity.providers.dao.salt. ReflectionSaltSource"> <property name="userPropertyToUse" value="userName" /> </bean> </property> </bean>

Hier wird die Eigenschaft userName des Benutzers als Salz zur Verschlüsselung des Pass-worts verwendet. Wichtig ist, dass das Salz statisch ist und sich niemals ändert. Andern-falls wäre es nämlich nicht möglich, den Benutzer zu authentifizieren (es sei denn, das Passwort würde nach einer Änderung des Salzes neu verschlüsselt). Während ReflectionSaltSource gewiss sicherer ist, ist SystemWideSaltSource we-sentlich einfacher und für die meisten Zwecke auch ausreichend. SystemWideSaltSource verwendet denselben Salzwert zur Verschlüsselung aller Benutzerpasswörter. Um eine SystemWideSaltSource zu verwenden, verschalten wir die Eigenschaft saltSource so:

<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="authenticationDao" /> <property name="passwordEncoder"> <bean class="org.acegisecurity.providers.encoding.Md5PasswordEncoder" /> </property> <property name="saltSource"> <bean class="org.acegisecurity.providers.dao.salt.SystemWideSaltSource"> <property name="systemWideSalt" value="ABC123XYZ789" /> </bean> </property> </bean>

In diesem Fall wird derselbe Schlüssel ABC123XYZ789 zur Verschlüsselung aller Pass-wörter verwendet.

Page 28: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.2 Benutzer authentifizieren

239

Caching von Benutzerangaben Jedes Mal, wenn eine geschützte Ressource angefordert wird, wird der Authentication-Manager aufgefordert, die Sicherheitsinformationen des Benutzers abzurufen. Wenn je-doch das Abrufen der Benutzerdaten auch das Abfragen einer Datenbank erfordert, kann das wiederholte Abfragen derselben Daten die Leistungsfähigkeit der Anwendung beein-trächtigen. In Anbetracht der Tatsache, dass sich die Daten zu einem Benutzer nicht allzu häufig ändern werden, kann es passender sein, diese Angaben nach dem ersten Abruf zwi-schenzuspeichern und sie bei jeder nachfolgenden Anfrage aus dem Cache abzurufen. Um das Caching von Benutzerdaten zu aktivieren, müssen wir DaoAuthentication-Provider eine Implementierung des Interfaces org.acegisecurity.providers.dao. UserCache übergeben. Bei diesem Interface ist die Implementierung dreier Methoden obligatorisch:

public UserDetails getUserFromCache(String username); public void putUserInCache(UserDetails user); public void removeUserFromCache(String username);

Die Methoden in diesem UserCache sind selbsterklärend: Sie gestatten das Eingeben, Ab-rufen und Entfernen von Benutzerdaten aus dem Cache. Es wäre ein Kinderspiel, eigene Implementierungen von UserCache zu schreiben. Allerdings bietet Spring Security zwei Convenience-Implementierungen, die Sie zuvor einmal ausprobieren sollten:

org.acegisecurity.providers.dao.cache.NullUserCache

org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache NullUserCache führt eigentlich überhaupt kein Caching durch. Stattdessen gibt die zuge-hörige Methode getUserFromCache() stets null zurück, wodurch das Abfragen der Be-nutzerdaten in der Datenbank durch DaoAuthenticationProvider erzwungen wird. Dies ist der von DaoAuthenticationProvider standardmäßig verwendete UserCache. EhCacheBasedUserCache stellt eine sinnvollere Cache-Implementierung dar. Wie der Name bereits sagt, basiert sie auf EHCache. Die Verwendung von EHCache mit Dao-AuthenticationProvider ist einfach. Verschalten Sie einfach eine EhCacheBasedUser-Cache-Bean in der DaoAuthenticationProvider-Eigenschaft userCache:

<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="authenticationDao" /> ... <property name="userCache"> <bean class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache"> <property name="cache" ref="ehcache" /> </bean> </property> </bean>

Die Eigenschaft cache referenziert eine ehcache-Bean, die ein EHCache-Cache-Objekt sein sollte. Eine Möglichkeit, ein solches Cache-Objekt zu erhalten, besteht darin, das Cache-Modul aus Spring Modules zu verwenden. Der folgende XML-Code beispielsweise verwendet Spring Modules zur Konfiguration von EHCache:

Page 29: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

240

<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheFactoryBean"> <property name="cacheManager" ref="cacheManager" /> <property name="cacheName" value="userCache" /> </bean> <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> <property name="configLocation" value="classpath:ehcache.xml" /> </bean>

Wie Sie aus Kapitel 5 vielleicht noch wissen, ist EhCacheFactoryBean aus Spring Modu-les eine Spring-Factory, die ein solches EHCache-Cache-Objekt erstellt. Die eigentliche Caching-Konfiguration ist in der Datei ehcache.xml enthalten, die aus dem Klassenpfad abgerufen wird. DaoAuthenticationProvider ist perfekt, wenn die Sicherheitsdaten Ihrer Anwendung in einer relationalen Datenbank abgelegt sind. Häufig jedoch ist die Sicherheitsarchitektur einer Anwendung so ausgelegt, dass die Authentifizierung im Zusammenspiel mit einem LDAP-Server erfolgt. Deswegen wollen wir uns nun ansehen, wie man den LdapAuthen-ticationProvider aus Spring Security verwendet. Wenn die Authentifizierung gegen-über LDAP erfolgen muss, ist Letzterer die bessere Wahl.

7.2.3 Authentifizierung gegenüber einem LDAP-Repository

Spring Security unterstützt die Authentifizierung gegenüber LDAP über LdapAuthen-ticationProvider, einen Authentifizierungsanbieter, der weiß, wie man die Anmelde-informationen eines Benutzers in einem LDAP-Repository verifiziert. Das folgende <bean>-Element veranschaulicht eine typische Konfiguration von LdapAuthenticati-onProvider:

<bean id="ldapAuthProvider" class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider"> <constructor-arg ref="authenticator" /> <constructor-arg ref="populator" /> </bean>

Wie Sie sehen, ist LdapAuthenticationProvider nicht besonders aufregend. Es sind keine Angaben darüber vorhanden, wie der LDAP-Server gefunden wird oder der Aus-gangskontext des Repositorys aussieht. Stattdessen wird LdapAuthenticationProvider via Konstruktorinjektion mit einem authenticator und einem populator verschaltet. Was sind das für Beans, und welchen Zweck haben sie? Tatsächlich behauptet LdapAuthenticationProvider zwar, über die Kommunikation mit einem LDAP-Repository Bescheid zu wissen, verlässt sich dabei aber auf zwei Strate-gieobjekte, die die eigentliche Arbeit erledigen:

Die Strategie-Bean authenticator behandelt die eigentliche Authentifizierung (d. h. die Verifizierung der Anmeldeinformationen) gegenüber dem LDAP-Repository. Der Authenticator kann ein beliebiges Objekt sein, das das Interface org.acegisecurity. providers.ldap.LdapAuthenticator implementiert.

Die Strategie-Bean populator ist für das Abrufen der einem Benutzer gewährten Be-rechtigungen aus dem LDAP-Repository zuständig. Der Populator kann ein beliebiges

Page 30: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.2 Benutzer authentifizieren

241

Objekt sein, das das Interface org.acegisecurity.providers.ldap.LdapAuthori-tiesPopulator implementiert.

Weil die Zuständigkeiten für Authentifizierung und Berechtigungen als Strategien außer-halb von LdapAuthenticationProvider definiert sind, können Sie die Strategieimple-mentierungen verschalten, die den Sicherheitsanforderungen Ihrer Anwendung am besten entsprechen. Aber wie sind die Beans authenticator und populator denn nun definiert? Betrachten wir zunächst die Bean authenticator, die die Authentifizierungsstrategie für Ldap-AuthenticationProvider definiert.

Authentifizierung mit LDAP-Bindung Wird die LDAP-Authentifizierung verwendet, so setzt man gemeinhin einen von zwei Ansätzen ein:

Bindung an den LDAP-Server mithilfe des Benutzernamens und Passworts eines LDAP-Benutzers

Abrufen eines Benutzereintrags in LDAP und Vergleich des eingegebenen Passworts mit einem Passwortattribut im LDAP-Datensatz

Um die Authentifizierung zu binden, bietet Spring Security eine LdapAuthenticator-Implementierung namens BindAuthenticator. BindAuthenticator verwendet den LDAP-Operator bind, um sich als Benutzer an den LDAP-Server zu binden. Dieser An-satz steht und fällt damit, dass der LDAP-Server die Anmeldeinformationen des Benutzers authentifiziert. Das folgende <bean>-Element deklariert einen BindAuthenticator in Spring:

<bean id="authenticator" class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator"> <constructor-arg ref="initialDirContextFactory" /> <property name="userDnPatterns"> <list> <value>uid={0},ou=motorists</value> </list> </property> </bean>

Hier haben wir den BindAuthenticator so deklariert, dass er über ein Konstruktorargu-ment und die Eigenschaft userDnPatterns injiziert wird. Auf das Konstruktorargument kommen wir sofort zurück; zunächst aber wollen wir die Eigenschaft userDnPatterns betrachten. Die Eigenschaft userDnPatterns wird verwendet, um BindAuthenticator mitzuteilen, wie ein Benutzer in LDAP gesucht wird. Sie nimmt eine Liste mit einem oder mehreren Patterns entgegen, die BindAuthenticator als DN (Distinguished Name) zur Benutzer-identifizierung verwendet. In diesem Fall benutzen wir nur ein einzelnes DN-Pattern, wie es in Abbildung 7.6 beschrieben ist.

Page 31: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

242

uid={0},ou=motorists

Benutzer-ID-Attribut

Organisationseinheit

"motorists"

Platzhalter für

Benutzer-ID

Abbildung 7.6 Für unsere Zwecke wird der DN eines Benutzers in die Benutzer-ID (UID) und die Organisationseinheit (Organizational Unit, OU) unterteilt.

Der Teil {0} im DN-Pattern ist ein Pattern-Argument, das als Platzhalter für den Benut-zernamen dient. Lautet der Benutzername etwa cwagon, dann heißt der DN, mit dem die Bindung an LDAP erfolgt, uid=cwagon,ou=motorists. Nun aber zum Konstruktorargument. Was ein BindAuthenticator wirklich wissen muss, um seine Aufgabe erledigen zu können, ist, wie er auf das LDAP-Repository zugreift. Aus diesem Grund wird er mit einem Konstruktorargument erstellt, das in initialDirCon-textFactory verschaltet ist. Die Deklaration sieht wie folgt aus:

<bean id="initialDirContextFactory" class="org.acegisecurity.ldap.DefaultInitialDirContextFactory"> <constructor-arg value="ldap://ldap.roadrantz.com:389/dc=roadrantz,dc=com"/> </bean>

DefaultInitialDirContextFactory fängt alle Angaben ab, die erforderlich sind, um eine Verbindung mit einem LDAP-Server herzustellen, und erstellt ein JNDI-DirContext-Objekt. Wenn Sie nicht allzu viel über JNDI oder DirContext wissen, ist dies auch nicht weiter tragisch. Vergessen Sie nur nicht, dass BindAuthenticator durch DefaultIniti-alDirContextFactory weiß, wie er an das LDAP-Repository gelangt. Das Konstruktorargument, mit dem DefaultInitialDirContextFactory erstellt wird, wird mit dem URL des LDAP-Providers verschaltet. In diesem Fall habe ich es mit einer Referenz auf den RoadRantz-LDAP-Server2 verschaltet und als Ausgangskontext als dc=roadrantz,dc=com angegeben. Der DN, mit dem die Benutzerdaten nachgeschlagen werden, ist relativ zu diesem Ausgangskontext.

Authentifizierung durch Vergleich von Passwörtern Eine Alternative zum Binden der Authentifizierung bietet Spring Security in Form der Authentifizierung eines Passwortvergleichs mithilfe von PasswordComparisonAuthen-ticator. PasswordComparisonAuthenticator vergleicht das eingegebene Passwort mit einem Passwortattribut (standardmäßig userPassword) im Datensatz des Benutzers. Die Konfiguration in Spring könnte etwa so aussehen:

<bean id="authenticator" class="org.acegisecurity.providers.ldap.authenticator. PasswordComparisonAuthenticator"> <constructor-arg ref="initialDirContextFactory" /> <property name="userDnPatterns"> <list> <value>uid={0},ou=motorists</value>

2 Denken Sie nicht einmal daran, den LDAP-Server unter ldap.roadrantz.com aufzurufen. Das ist nur ein

Beispiel – Sie finden unter der Adresse keinen LDAP-Provider.

Page 32: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.2 Benutzer authentifizieren

243

</list> </property> </bean>

Beachten Sie, dass diese PasswordComparisonAuthenticator-Deklaration mit Ausnah-me des Klassennamens identisch mit der Deklaration von BindAuthenticator ist. Das liegt daran, dass beide in ihrer einfachsten Form mehr oder minder gleich sind. Beide benötigen eine Ausgangskontext-Factory, damit sie wissen, wie sie mit dem LDAP-Repository kommunizieren, und beide benötigen ein oder mehrere DN-Patterns zum Auf-finden der Benutzerdatensätze. Aber es gibt einige weitere Eigenschaften, um PasswordComparisonAuthenticator an-zupassen. Wenn beispielsweise das Standardattribut userPassword Ihre Anforderungen nicht erfüllt, können Sie es durch Verschalten eines neuen Wertes in der Eigenschaft passwordAttributeName überschreiben. Deklarieren Sie beispielsweise PasswordCom-parisonAuthenticator wie folgt, um das Passwort mit einem Attribut namens user-Credentials zu vergleichen:

<bean id="authenticator" class="org.acegisecurity.providers.ldap.authenticator. PasswordComparisonAuthenticator"> <constructor-arg ref="initialDirContextFactory" /> <property name="userDnPatterns"> <list> <value>uid={0},ou=motorists</value> </list> </property> <property name="passwordAttributeName" value="userCredentials" /> </bean>

Ferner können Sie etwa festlegen, wie das Passwort in LDAP kodiert wird. Standardmäßig verwendet PasswordComparisonAuthenticator den LdapShaPasswordEncoder aus Spring Security zur Kodierung des Passworts vor dem Vergleich. LdapShaPassword-Encoder unterstützt für LDAP die SHA- und die SSHA-Kodierungen. Wenn diese Ihren Ansprüchen jedoch nicht genügen, lässt sich auch jede andere Implementierung von org.acegisecurity.providers.encoding.PasswordEncoder – darunter auch die in Tabelle 7.2 aufgeführten – in der Eigenschaft passwordEncoder verschalten. Wenn Sie Ihr Passwort in LDAP unverschlüsselt speichern (was nicht ratsam, aber durch-aus möglich ist), deklarieren Sie PasswordComparisonAuthenticator wie folgt:

<bean id="authenticator" class="org.acegisecurity.providers.ldap.authenticator. PasswordComparisonAuthenticator"> <constructor-arg ref="initialDirContextFactory" /> <property name="userDnPatterns"> <list> <value>uid={0},ou=motorists</value> </list> </property> <property name="passwordEncoder"> <bean class="org.acegisecurity.providers.encoding. PlaintextPasswordEncoder" /> </property> </bean>

Bevor wir nun mit der Strategie-Bean populator fortfahren, wollen wir zumindest eine Optimierung an der Bean initialDirContextFactory vornehmen.

Page 33: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

244

Anders als BindAuthenticator wird PasswordComparisonAuthenticator nicht über den Benutzer-DN an LDAP gebunden. Manche LDAP-Provider gestatten eine anonyme Bindung, wobei initialDirContextFactory in einem solchen Fall ohne Änderung funk-tioniert. Aus Sicherheitsgründen gestatten die meisten Provider jedoch keine anonyme Bindung, weswegen wir einen Manager-DN und ein Passwort für die Bindung mit De-faultInitialDirContextFactory angeben müssen:

<bean id="initialDirContextFactory" class="org.acegisecurity.ldap.DefaultInitialDirContextFactory"> <constructor-arg value="ldap://ldap.roadrantz.com:389/dc=roadrantz,dc=com"/> <property name="managerDn" value="cn=manager,dc=roadrantz,dc=com" /> <property name="managerPassword" value="letmein" /> </bean>

Wenn DefaultInitialDirContextFactory auf LDAP zugreift, wird es als Manager gebunden und agiert beim Vergleich des Benutzerpassworts im Auftrag des Benutzers. So, und nun wollen wir die Strategie-Bean populator konfigurieren, um das Bild der LDAP-Authentifizierung abzurunden.

Die Strategie-Bean populator Die Authentifizierung des Benutzers ist nur der erste Schritt, der von LdapAuthenticati-onProvider durchgeführt wird. Sobald die Identität des Benutzers bestätigt ist, muss LdapAuthenticationProvider eine Liste der dem Benutzer gewährten Berechtigungen abrufen, um festzustellen, welche Rechte der Benutzer innerhalb der Anwendung genießt. Wie bei der Authentifizierung verwendet LdapAuthenticatorProvider ein Strategie-objekt, um die dem Benutzer gewährten Berechtigungen aus LDAP abzurufen. Spring Se-curity bietet eine Implementierung des Interfaces LdapAuthoritiesPopulator: Default-LdapAuthoritiesPopulator. DefaultLdapAuthoritiesPopulator wird in Spring wie folgt konfiguriert:

<bean id="populator" class="org.acegisecurity.providers.ldap.populator. DefaultLdapAuthoritiesPopulator"> <constructor-arg ref="initialDirContextFactory" /> <constructor-arg value="ou=groups" /> <property name="groupRoleAttribute" value="ou" /> </bean>

Das Erste, was Sie bemerken werden, ist, dass DefaultLdapAuthoritiesPopulator mit zwei Konstruktorargumenten konstruiert wird, deren erstes eine Referenz auf unseren alten Freund initialDirContextFactory ist. Ebenso wie die Strategie-Bean authenticator muss populator wissen, wie es auf das LDAP-Repository zugreifen kann, um die dem Benutzer gewährten Berechtigungen abzurufen. Das zweite Konstruktorargument unterstützt DefaultLdapAuthoritiesPopulator bei der Suche nach Gruppen innerhalb des LDAP-Repositorys. Da ein LDAP-Repository sei-nem Wesen nach hierarchisch ist, können sich Sicherheitsgruppen überall befinden. Dieses Konstruktorargument gibt einen Basis-DN an, von dem ausgehend nach Gruppen gesucht wird. Dieser Basis-DN ist relativ zum Ausgangskontext. Deswegen werden wir, wenn der

Page 34: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.2 Benutzer authentifizieren

245

Basis-DN der Gruppe ou=groups lautet, nach Gruppen in ou=groups,dc=roadrantz, dc=com suchen. Die Eigenschaft groupRoleAttribute schließlich gibt den Namen des Attributs an, das Rolleninformationen enthält (diese werden letztendlich in die Berechtigungen übersetzt, die einem Benutzer gewährt werden). Der Standardwert lautet cn, in unserem Beispiel jedoch legen wir ihn auf ou fest. Auf diese Weise konfiguriert, ruft DefaultLdapAuthoritiesPopulator alle Gruppen ab, bei denen der Benutzer Mitglied ist – also alle Gruppen, die ein member-Attribut mit dem DN des Benutzers enthalten. Nehmen wir etwa an, Sie haben ein LDAP-Repository mit dem folgenden LDIF3 ausge-füllt:

dn: ou=groups,dc=roadrantz,dc=com objectClass: top objectClass: organizationalUnit ou: groups dn: cn=motorists,ou=groups,dc=roadrantz,dc=com objectClass: groupOfNames objectClass: top cn: motorists description: Acegi Security Motorists member: uid=craig,ou=people,dc=roadrantz,dc=com member: uid=raymie,ou=people,dc=roadrantz,dc=com ou: motorist dn: cn=vips,ou=groups,dc=roadrantz,dc=com objectClass: groupOfNames objectClass: top cn: vips description: Acegi Security Motorists member: uid=craig,ou=people,dc=roadrantz,dc=com ou: vip

Wenn der Benutzer namens craig authentifiziert wird, werden ihm die Rollen ROLE_ MOTORIST und ROLE_VIP zugewiesen. Wird hingegen raymie authentifiziert, dann werden ihr nur die Berechtigungen von ROLE_MOTORIST zugewiesen, da die Gruppe vips ihren DN nicht als member-Attribut enthält. Beachten Sie, dass der Gruppenname (im Attribut ou) in Großbuchstaben konvertiert und ihm dann das Präfix ROLE_ vorangestellt wird. Die Normalisierung der Schreibweise ist lediglich eine Convenience-Funktion, die das Ermitteln der Berechtigungen eines Benut-zers unabhängig von der verwendeten Groß-/Kleinschreibung erleichtert. Sie können die-ses Verhalten deaktivieren, indem Sie die Eigenschaft convertToUpperCase auf false setzen. Das Präfix ROLE_ wird für den RoleVoter benutzt, über den wir in Abschnitt 7.3.2 noch sprechen werden. Wenn Sie ein anderes Rollenpräfix benutzen wollen, können Sie die Eigenschaft rolePrefix von DefaultLdapAuthoritiesPopulator nach Belieben kon-figurieren.

3 Der Kenner weiß: LDIF (LDAP Data Interchange Format) ist das Standardverfahren zur Darstellung von

LDAP-Verzeichnisinhalten.

Page 35: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

246

Um etwa die Umstellung auf die Großschreibung zu deaktivieren und das Rollenpräfix in GROUP_ umzuändern, konfigurieren Sie DefaultLdapAuthoritiesPopulator wie folgt:

<bean id="populator" class="org.acegisecurity.providers.ldap.populator. DefaultLdapAuthoritiesPopulator"> <constructor-arg ref="initialDirContextFactory" /> <constructor-arg value="ou=groups" /> <property name="groupRoleAttribute" value="ou" /> <property name="convertToUpperCase" value="false" /> <property name="rolePrefix" value="GROUP_" /> </bean>

Eine weitere Optimierung, die Sie an DefaultLdapAuthoritiesPopulator vornehmen sollten, ist die Art und Weise, wie es nach Mitgliedern sucht. Normalerweise wird nach Gruppen gesucht, deren Attribut member den DN des Benutzers enthält. Dies ist in Ord-nung, sofern Ihr LDAP so eingerichtet ist, dass er dieses Attribut entsprechend benutzt. Aber angenommen, Ihr LDAP-Repository verwendet statt member ein Attribut associate zur Kontrolle der Mitgliedschaft. In diesem Fall sollten Sie die Eigenschaft groupSearch-Filter wie folgt festlegen:

<bean id="populator" class="org.acegisecurity.providers.ldap.populator. DefaultLdapAuthoritiesPopulator"> <constructor-arg ref="initialDirContextFactory" /> <constructor-arg value="ou=groups" /> <property name="groupRoleAttribute" value="ou" /> <property name="convertToUpperCase" value="false" /> <property name="rolePrefix" value="GROUP_" /> <property name="groupSearchFilter" value="(associate={0})" /> </bean>

Beachten Sie, dass die Eigenschaft groupSearchFilter das Pattern-Argument {0} zur Darstellung des Benutzer-DN verwendet. Nun haben wir Beans in der Authentifizierungsverarbeitung von Spring Security verschal-tet, um den Benutzer zu identifizieren. Als Nächstes wollen wir sehen, wie Spring Security ermittelt, ob ein authentifizierter Benutzer ausreichende Berechtigungen genießt, um auf eine abgesicherte Ressource zuzugreifen.

7.3 Zugriffssteuerung

Authentifizierung ist nur der erste Schritt in Spring Security. Wenn Spring Security herausgefunden hat, wer der Benutzer ist, muss es entscheiden, ob Zugriff auf die abge-sicherten Ressourcen gewährt wird oder nicht. Wir haben den Authentication-Manager aus Abbildung 7.1 mittlerweile konfiguriert. Nun wollen wir mit der Konfiguration des Access-Decision-Managers fortfahren. Der Access-Decision-Manager entscheidet, ob der Benutzer ausreichende Rechte genießt, um auf abgesicherte Ressourcen zuzugreifen. Access-Decision-Manager werden über das Interface org.acegisecurity.AccessDeci-sionManager definiert:

public interface AccessDecisionManager { public void decide(Authentication authentication, Object object,

Page 36: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.3 Zugriffssteuerung

247

ConfigAttributeDefinition config) throws AccessDeniedException, InsufficientAuthenticationException; public boolean supports(ConfigAttribute attribute); public boolean supports(Class clazz); }

Die supports()-Methoden überprüfen den Klassentyp und die Konfigurationsattribute (d. h. die Zugriffsanfragen der Ressource), um zu bestimmen, ob der Access-Decision-Manager in der Lage ist, die Zugriffsentscheidungen für die Ressource zu treffen. Die Methode decide() trifft dann die endgültige Entscheidung. Kehrt sie zurück, ohne eine AccessDeniedException oder eine InsufficientAuthenticationException ausge-löst zu haben, so wird der Zugriff auf die geschützte Ressource gewährt. Andernfalls wird der Zugriff verweigert.

7.3.1 Über Zugriffsentscheidungen abstimmen

Die Access-Decision-Manager in Spring Security sind letztendlich dafür verantwortlich, die Zugriffsrechte eines authentifizierten Benutzers zu ermitteln. Diese Entscheidung jedoch treffen sie nicht allein. Stattdessen fragen sie ein oder mehrere Objekte ab, die darüber abstimmen, ob einem Benutzer der Zugriff auf eine abgesicherte Ressource ge-währt wird oder nicht. Sobald alle Stimmen eingegangen sind, zählt der Access-Decision-Manager aus und erhält so das endgültige Ergebnis. Spring Security bietet drei Implementierungen von AccessDecisionManager, die in Tabelle 7.3 aufgelistet sind. Jede dieser Implementierungen verfolgt beim Auszählen einen anderen Ansatz.

Tabelle 7.3 Access-Decision-Manager helfen in Spring Security bei der Entscheidung, ob einem Be-nutzer der Zugriff auf eine Ressource zu gewähren ist, indem darüber abgestimmt wird.

Access-Decision-Manager Art der Entscheidung

org.acegisecurity.vote.AffirmativeBased Gewährt Zugriff, wenn mindestens ein Abstimmender für den Zugriff stimmt.

org.acegisecurity.vote.ConsensusBased Gewährt Zugriff, wenn die Abstimmenden mehrheitlich für den Zugriff stimmen.

org.acegisecurity.vote.UnanimousBased Gewährt Zugriff, wenn alle Abstimmen-den für den Zugriff stimmen.

Alle Access-Decision-Manager werden in der Spring-Konfigurationsdatei auf identische Weise konfiguriert. Der folgende XML-Auszug etwa konfiguriert einen Access-Decision-Manager des Typs UnanimousBased:

<bean id="accessDecisionManager" class="org.acegisecurity.vote.UnanimousBased"> <property name="decisionVoters"> <list> <ref bean="roleVoter"/> </list> </property> </bean>

Page 37: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

248

Über die Eigenschaft decisionVoters übergeben Sie dem Access-Decision-Manager eine Liste der Wähler (engl. Voter). In diesem Fall gibt es nur einen Wähler: eine Referenz auf eine Bean namens roleVoter. Wir wollen einmal sehen, wie der roleVoter konfiguriert wird.

7.3.2 Die Stimmabgabe

Zwar haben die über die Zugriffsentscheidung abstimmenden Wähler nicht das letzte Wort darüber, ob der Zugriff auf eine abgesicherte Ressource gewährt wird (dies ist die Aufgabe des Access-Decision-Managers), aber sie spielen eine wichtige Rolle im Prozess der Ent-scheidungsfindung. Aufgabe eines Wählers ist es, die dem Benutzer gewährten Berechti-gungen mit den Berechtigungen zu vergleichen, die von den Konfigurationsattributen der betreffenden Ressource verlangt werden. Basierend auf diesen Angaben gibt der Wähler seine Stimme ab, die der Access-Decision-Manager dann bei der Entscheidungsfindung nutzen kann. Ein Wähler ist ein beliebiges Objekt, das das Interface org.acegisecurity.vote. AccessDecisionVoter implementiert:

public interface AccessDecisionVoter { public static final int ACCESS_GRANTED = 1; public static final int ACCESS_ABSTAIN = 0; public static final int ACCESS_DENIED = -1; public boolean supports(ConfigAttribute attribute); public boolean supports(Class clazz); public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config); }

Wie Sie sehen, ähnelt das Interface AccessDecisionVoter der von AccessDecisionMa-nager. Der wesentliche Unterschied besteht darin, dass statt einer Methode decide(), die void zurückgibt, eine Methode vote() mit einem int-Rückgabewert vorhanden ist. Das liegt daran, dass ein Wähler nicht entscheiden kann, ob Zugriff gewährt werden soll oder nicht – er gibt nur seine Stimme zu dieser Frage ab. Wenn der Wähler vor der Entscheidung steht, eine Stimme abzugeben, kann er dies auf eine von drei Arten tun:

ACCESS_GRANTED. Der Wähler möchte den Zugriff auf die abgesicherte Ressource ge-währen.

ACCESS_DENIED. Der Wähler möchte den Zugriff auf die abgesicherte Ressource ver-weigern.

ACCESS_ABSTAIN. Der Wähler enthält sich. Wie bei den meisten Spring Security-Komponenten können Sie nach Belieben eigene Im-plementierungen von AccessDecisionVoter schreiben. Allerdings hat Spring Security bereits RoleVoter im Gepäck. Hierbei handelt es sich um eine praktische Implementie-rung, die abstimmt, wenn die Konfigurationsattribute der abgesicherten Ressource eine Rolle darstellen. Genauer gesagt, nimmt RoleVoter an der Abstimmung teil, wenn die Ressource über ein Konfigurationsattribut verfügt, dessen Name mit ROLE_ beginnt.

Page 38: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.3 Zugriffssteuerung

249

RoleVoter legt sein Abstimmverhalten fest, indem es einfach alle Konfigurationsattribute der betreffenden Ressource (d. h. all diejenigen, die mit ROLE_ beginnen) mit allen Berech-tigungen vergleicht, die dem authentifizierten Benutzer gewährt werden. Findet Role-Voter eine Übereinstimmung, dann wird die Stimme ACCESS_GRANTED abgegeben. An-dernfalls lautet das Urteil ACCESS_ DENIED. Der RoleVoter gibt nur dann keine Stimme ab, wenn den Berechtigungen, die für den Zugriff erforderlich sind, nicht das Präfix ROLE_ vorangestellt ist. Wenn etwa die abgesi-cherte Ressource lediglich Berechtigungen erfordert, die keine Rollen sind (z. B. CREA-TE_USER), führt der RoleVoter keine Abstimmung durch. Sie können einen RoleVoter mit dem folgenden XML-Code in der Spring-Konfigura-tionsdatei konfigurieren:

<bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter"/>

Wie bereits weiter oben erwähnt, stimmt der RoleVoter nur ab, wenn die geschützte Ressource über Konfigurationsattribute verfügt, denen ROLE_ vorangestellt ist. Allerdings ist dieses Präfix nur eine Vorgabe, die Sie mit der Eigenschaft rolePrefix überschreiben können:

<bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter"> <property name="rolePrefix" value="GROUP_" /> </bean>

Hier wurde das Standardpräfix mit GROUP_ überschrieben. Aus diesem Grund gibt der RoleVoter nachfolgend nur eine Stimme zur Autorisierung für Berechtigungen ab, deren Name mit GROUP_ beginnt.

7.3.3 Stimmenthaltungen

Da Sie nun wissen, dass jeder Wähler für das Gewähren oder Verweigern des Zugriffs stimmen, sich seiner Stimme aber auch enthalten kann, fragen Sie sich vielleicht, was passiert, wenn kein Wähler von seinem Abstimmungsrecht Gebrauch macht. Erhält der Benutzer in diesem Fall Zugriff oder nicht? Standardmäßig verweigern alle Access-Decision-Manager den Zugriff auf eine Ressource, wenn alle Wähler sich enthalten. Allerdings können Sie dieses Verhalten außer Kraft setzen, indem Sie die Eigenschaft allowIfAllAbstain des Access-Decision-Managers auf true festlegen:

<bean id="accessDecisionManager" class="org.acegisecurity.vote.UnanimousBased"> <property name="decisionVoters"> <list> <ref bean="roleVoter"/> </list> </property> <property name="allowIfAllAbstain" value="true" /> </bean>

Page 39: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

250

Durch diese Vorgehensweise implementieren Sie ein Prinzip der „schweigenden Zustim-mung“. Mit anderen Worten: Wenn kein Wähler seine Stimme abgibt, geht man davon aus, dass alle der Gewährung des Zugriffs zugestimmt haben. Nachdem wir nun gesehen haben, wie Authentication- und Access-Decision-Manager in Spring Security funktionieren, wollen wir sie auch einsetzen. Im nächsten Abschnitt erfah-ren Sie, wie man die Spring Security-Sammlung der Servlet-Filter zum Absichern einer Webanwendung benutzt. Später – in Abschnitt 7.6 – werden wir uns eine Anwendung genauer ansehen und untersuchen, wie man mit Spring AOP Sicherheit auf der Ebene des Methodenaufrufs implementiert.

7.4 Webanwendungen absichern

Der Websicherheits-Support durch Spring Security basiert in erster Linie auf Servlet-Filtern. Diese Filter fangen eingehende Anfragen ab und verarbeiten sie sicherheitstech-nisch, bevor die Anfrage von Ihrer Anwendung behandelt wird. Spring Security enthält eine Handvoll Filter, die Servlet-Anfragen abfangen und sie an die Authentication- oder Access-Decision-Manager weiterleiten, um die Sicherheit zu erzwingen. Je nach Ihren Bedürfnissen können Sie mehrere der in Tabelle 7.4 aufgeführten Filter zur Absicherung Ihrer Anwendung verwenden.

Tabelle 7.4 Spring Security steuert den Zugriff auf Webanwendungen über diverse Servlet-Filter.

Filter (org.acegisecurity.*) Zweck

adapters. HttpRequestIntegrationFilter

Füllt den Sicherheitskontext mit Angaben aus dem Benutzerprinzipal aus, der vom Webcontainer bereitgestellt wird.

captcha. CaptchaValidationProcessingFilter

Unterstützt die Erkennung eines Benutzers als Mensch (im Gegensatz zum automatisier-ten Prozess) mithilfe von Captcha-Techniken. Captcha ist eine Technik, mit deren Hilfe menschliche Benutzer von automatisierten bzw. computergetriebenen Benutzern unter-schieden werden. Hierzu wird der Benutzer aufgefordert, etwas zu benennen, das für Menschen leicht, für Computer jedoch schwer erkennbar ist (meist ein Bild).

concurrent.ConcurrentSessionFilter Stellt sicher, dass ein Benutzer eine maxima-le Anzahl gleichzeitiger Anmeldungen nicht überschreitet.

context. HttpSessionContextIntegrationFilter

Füllt den Sicherheitskontext mit Daten aus, die aus HttpSession gewonnen werden.

intercept.web. FilterSecurityInterceptor

Spielt die Rolle des Sicherheits-Interceptors, der entscheidet, ob der Zugriff auf eine ge-schützte Ressource gestattet wird oder nicht.

Page 40: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.4 Webanwendungen absichern

251

Filter (org.acegisecurity.*) Zweck

providers.anonymous. AnonymousAuthenticationProvider

Dient der Festlegung eines nicht authentifi-zierten Benutzers als anonymer Benutzer.

securechannel. ChannelProcessingFilter

Stellt sicher, dass eine Anfrage je nach Bedarf via HTTP oder HTTPS gesendet wird.

ui.basicauth.BasicProcessingFilter Versucht, einen Benutzer durch Verarbeitung einer HTTP-Basisauthentifizierung zu authen-tifizieren.

ui.cas.CasProcessingFilter Authentifiziert einen Benutzer durch Verarbei-tung eines CAS-Tickets (Central Authentica-tion Service).

ui.digestauth.DigestProcessingFilter Versucht, einen Benutzer durch Verarbeitung einer HTTP-Digest-Authentifizierung zu au-thentifizieren.

ui.ExceptionTranslationFilter Behandelt AccessDeniedExceptions und AuthenticationExceptions, die von einem der anderen Filter in der Filterkette ausgelöst wurden.

ui.logout.LogoutFilter Dient dem Abmelden eines Benutzers von der Anwendung.

ui.rememberme. RememberMeProcessingFilter

Authentifiziert automatisch Benutzer, die darum gebeten haben, von der Anwendung „vermerkt“ zu werden.

ui.switchuser. SwitchUserProcessingFilter

Dient dem Wechseln des Benutzers. Die Funktionalität ähnelt dem Unix-Befehl su.

ui.webapp. AuthenticationProcessingFilter

Akzeptiert Prinzipal und Anmeldeinformatio-nen eines Benutzers und versucht, diesen zu authentifizieren.

ui.webapp.SiteminderAuthentication- ProcessingFilter

Authentifiziert einen Benutzer durch Verarbei-tung der CA-/Netegrity SiteMinder-Header.

ui.x509.X509ProcessingFilter Authentifiziert einen Benutzer durch Verarbei-tung eines X.509-Zertifikats, das von einem Clientwebbrowser übermittelt wurde.

wrapper.SecurityContextHolder- AwareRequestFilter

Füllt die Servlet-Anfrage mit einem Anfrage-Wrapper aus.

Zwar führt Tabelle 7.4 alle 17 in Spring Security vorhandenen Filter auf, aber die meisten Anwendungen werden sich mit einer Handvoll davon zufrieden geben. Wenn eine Anfrage an eine mit Spring abgesicherte Webanwendung übermittelt wird, durchläuft sie zumindest die folgenden vier Filter (vgl. Abbildung 7.7 auf der nächsten Seite):

Page 41: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

252

Anfrage

Integrationsfilter1

authentifizierungsverarbeitender

Filter

2

Exception-Übersetzungsfilter3

Filtersicherheits-Interceptor4

Abgesicherte Webressource

Antwort

Abbildung 7.7 Weg einer Anfrage durch die wichtigsten Filter von Spring Security

1. Aufgrund des zustandslosen Wesens von HTTP benötigt Spring Security eine Mög-lichkeit, die Authentifizierung eines Benutzers über die diversen Webanfragen hinweg aufrechtzuerhalten. Ein Integrationsfilter ist dafür zuständig, eine zuvor (meist in der HTTP-Session) gespeicherte Authentifizierung zu Beginn einer Anfrage abzurufen, sodass diese von den anderen Spring Security-Filtern verarbeitet werden kann.

2. Daraufhin bestimmt einer der authentifizierungsverarbeitenden Filter, ob die Anfrage eine Authentifizierungsanfrage ist. Ist dies der Fall, dann werden die entsprechenden Benutzerdaten (meist eine Kombination aus Benutzername und Passwort) der Anfrage entnommen und an den Authentication-Manager übergeben, damit dieser die Identität des Benutzers verifizieren kann. Handelt es sich um keine Authentifizierungsanfrage, dann führt auch der Filter keine Verarbeitung durch, und die Anfrage wird in der Filterkette weitergereicht.

3. Der nächste Filter in der Reihe ist der Exception-Übersetzungsfilter. Dessen einzige Aufgabe besteht darin, eventuell ausgelöste AccessDeniedExceptions und Authen-ticationExceptions in die entsprechenden HTTP-Antworten zu übersetzen. Bei Erkennen einer AuthenticationException wird die Anfrage an einen Authentifizie-rungs-Eintrittspunkt (z. B. einen Anmeldedialog) gesendet. Wird eine AccessDenied-Exception ausgelöst, so erfolgt standardmäßig eine HTTP-Antwort mit dem Fehlerco-de 403 an den Browser.

4. Der letzte erforderliche Filter ist der Filtersicherheits-Interceptor. Dieser Filter spielt die Rolle des Sicherheits-Interceptors (siehe Abschnitt 7.1.1) für Webanwendungen. Seine Aufgabe besteht darin, die Anfrage zu untersuchen und zu bestimmen, ob der Benutzer die erforderlichen Berechtigungen genießt, um auf die abgesicherte Ressour-ce zuzugreifen. Allerdings funktioniert er nicht für sich allein, sondern ist auf den Authentication- und den Access-Decision-Manager angewiesen, die ihm dabei helfen, Zugriff auf die Ressource zu gewähren oder zu verweigern.

Page 42: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.4 Webanwendungen absichern

253

Wenn der Benutzer nun den Filtersicherheits-Interceptor hinter sich gebracht hat, erhält er Zugriff auf die geschützte Webressource. Gelingt es ihm jedoch nicht, so wird eine AccessDeniedException ausgelöst, die der Exception-Übersetzungsfilter dann entspre-chend behandelt. Wir wollen uns diese Filter nun genauer ansehen. Bevor Sie sie jedoch einsetzen können, müssen Sie erst einmal wissen, wie Spring Security Servlet-Filter Spring-spezifisch einsetzt.

7.4.1 Proxys für Spring Security-Filter erstellen

Wenn Sie Servlet-Filter schon einmal verwendet haben, ist Ihnen bekannt, dass Sie diese Filter, um sie wirkungsvoll einzusetzen, in der Datei web.xml der Webanwendung konfigu-rieren müssen. Hierfür setzen Sie die Konfigurationselemente <filter> und <filter-mapping> ein. Dies funktioniert zwar, ist für die Konfiguration mit DI allerdings weniger geeignet. Nehmen wir an, Sie haben in Ihrer Datei web.xml den folgenden Filter deklariert:

<filter> <filter-name>Foo</filter-name> <filter-class>FooFilter</filter-class> </filter>

Nehmen wir nun ferner an, dass FooFilter eine Referenz auf eine Bean Bar benötigt, um seine Arbeit tun zu können. Wie injizieren Sie eine Instanz von Bar in FooFilter? Die kurze Antwort lautet: Geht nicht. Die Datei web.xml hat keine Ahnung von der Dependency Injection, und es gibt auch keine direkte Möglichkeit, Beans aus dem Spring-Anwendungskontext abzurufen und sie in einem Servlet-Filter zu verschalten. Die einzige Option, die Ihnen zur Verfügung steht, besteht darin, die Bean bar mithilfe von Springs WebApplicationContextUtils aus dem Spring-Kontext abzurufen. Sie können im Filter-code beispielsweise wie folgt formulieren:

ApplicationContext ctx = WebApplicationContextUtils. getWebApplicationContext(servletContext); Bar bar = (Bar) ctx.getBean("bar");

Das Problem bei diesem Ansatz besteht jedoch darin, dass Sie Spring-spezifischen Code in Ihr Servlet-Filter einkodieren müssen. Außerdem haben Sie am Ende eine Referenz auf den Namen der Bean bar fest einkodiert. Zum Glück bietet Spring Security eine bessere Möglichkeit – über FilterToBeanProxy.

Proxys für Servlet-Filter erstellen FilterToBeanProxy ist ein spezieller Servlet-Filter, der für sich genommen nicht allzu viel leistet. Stattdessen delegiert er, wie Abbildung 7.8 zeigt, seine Arbeit an eine Bean im Spring-Anwendungskontext. Die Delegate-Bean implementiert das Interface javax.serv-let.Filter wie jeden anderen Servlet-Filter, wird aber nicht in der Datei web.xml kon-figuriert, sondern in der Spring-Konfigurationsdatei.

Page 43: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

254

Spring-AnwendungskontextServlet-Kontext

FilterToBeanProxydurch Spring injizierter

Filter

delegiert an

Abbildung 7.8 FilterToBeanProxy übergibt die Filterbehandlung an eine Delegate-Filter-Bean, die als Proxy im Spring-Anwendungskontext agiert.

Mit FilterToBeanProxy können Sie den eigentlichen Filter in Spring konfigurieren und dabei uneingeschränkt vom Spring-Support für die Dependency Injection profitieren. Die Datei web.xml enthält lediglich die <filter>-Deklaration für FilterToBeanProxy. Der eigentliche FooFilter wird in der Spring-Konfigurationsdatei konfiguriert und verwendet die Setter-Injektion, um der Eigenschaft bar eine Referenz auf eine Bar-Bean zu injizieren. Um FilterToBeanProxy verwenden zu können, müssen Sie einen <filter>-Eintrag in der Datei web.xml der Webanwendung vornehmen. Wenn Sie beispielsweise einen Foo-Filter mit FilterToBeanProxy konfigurieren, würden Sie den folgenden Code verwen-den:

<filter> <filter-name>Foo</filter-name> <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class> <init-param> <param-name>targetClass</param-name> <param-value> com.roadrantz.FooFilter </param-value> </init-param> </filter>

Hier ist als Initialisierungsparameter targetClass der vollqualifizierte Klassenname der Delegate-Filter-Bean festgelegt. Wenn dieser FilterToBeanProxy initialisiert wird, sucht er nach einer Bean im Spring-Kontext, deren Typ FooFilter ist. FilterToBeanProxy delegiert seine Filteraufgaben dann an die FooFilter-Bean, die Bestandteil des Spring-Kontexts ist:

<bean id="fooFilter" class="com.roadrantz.FooFilter"> <property name="bar" ref="bar" /> </bean>

Wenn eine FooFilter-Bean nicht gefunden werden kann, wird eine Exception ausgelöst. Werden mehrere passende Beans erkannt, dann wird die erste Bean verwendet, die gefun-den wurde. Optional können Sie als Initialisierungsparameter targetBean statt targetClass fest-legen, um eine bestimmte Bean aus dem Spring-Kontext herauszusuchen. Wenn Sie bei-spielsweise die fooFilter-Bean nach ihrem Namen ermitteln wollen, tun Sie dies durch Festlegen von targetBean wie folgt:

<filter> <filter-name>Foo</filter-name> <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class> <init-param> <param-name>targetBean</param-name> <param-value>fooFilter</param-value>

Page 44: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.4 Webanwendungen absichern

255

</init-param> </filter>

Der Initialisierungsparameter targetBean ermöglicht Ihnen eine genauere Angabe dazu, an welche Bean das Filtern delegiert werden soll; allerdings ist es notwendig, dass die Namen des Delegates in der Datei web.xml und der Spring-Konfigurationsdatei vollständig übereinstimmen müssen. Sie haben dann mehr Arbeit, wenn Sie beschließen, die Bean umzubenennen. Aus diesem Grund empfiehlt es sich wahrscheinlich eher, targetClass anstelle von targetBean zu verwenden.

Hinweis Vielleicht ist es für Sie interessant, zu erfahren, dass an FilterToBeanProxy nichts für Spring Security oder die Absicherung von Webanwendungen Spezifisches vorhanden ist. FilterToBeanProxy kann sich insbesondere dann als praktisch erweisen, wenn Sie eigene Servlet-Filter konfigurieren. Und weil er so nützlich ist, wurde ein ähnlich benannter Filter namens org.springframework.web.filter.DelegatingFilterProxy in Version 1.2 von Spring hinzugefügt.

Abschließend müssen Sie den Filter mit einem URL-Pattern verknüpfen. Das folgende <filter-mapping>-Element bindet die FilterToBeanProxy-Instanz Foo an das URL-Pattern /* (auf diese Weise werden alle Anfragen verarbeitet):

<filter-mapping> <filter-name>Foo</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

Unabhängig davon, ob Sie sich für targetClass oder targetBean entscheiden, muss FilterToBeanProxy in der Lage sein, auf den Spring-Anwendungskontext zuzugreifen. Das bedeutet, dass der Spring-Kontext mit Springs ContextLoaderListener oder Con-textLoaderServlet geladen werden muss (siehe Kapitel 13). Da Sie jetzt wissen, wie FilterToBeanProxy funktioniert, brennt Ihnen wahrscheinlich eine Frage unter den Nägeln: Was hat all dies mit Spring Security zu tun? Gut, dass Sie fragen. Wie ich bereits erwähnte, verwendet Spring Security Servlet-Filter, um die Websicherheit durchzusetzen. Jedem dieser Filter müssen andere Beans aus dem Spring-Anwendungs-kontext injiziert werden, damit sie ihre Arbeit erledigen können. So müssen etwa dem FilterSecurityInterceptor die Beans AuthenticationManager und AccessDeci-sionManager injiziert werden, damit er die Sicherheit gewährleisten kann. Leider macht es die Servlet-Spezifikation nicht einfach, eine Dependency Injection an Servlet-Filtern durchzuführen. FilterToBeanProxy löst dieses Problem, denn es agiert als „Frontmann“ für die echten Filter, die als Beans im Spring-Anwendungskontext konfiguriert werden.

Proxys für mehrere Filter erstellen Wahrscheinlich fragen Sie sich jetzt, was, wenn FilterToBeanProxy Anfragen behandelt, indem ein Proxy für eine Spring-konfigurierte Bean erstellt wird, am anderen Ende – also bei Spring – geschieht. Sehr gute Frage.

Page 45: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

256

Eigentlich kann die Bean, für die FilterToBeanProxy einen Proxy erstellt, eine beliebige Implementierung von javax.servlet.Filter sein, beispielsweise ein Spring Security-Filter oder ein selbsterstellter Filter. Doch wie ich bereits sagte, erfordert Spring Security die Konfiguration von mindestens vier und möglicherweise sogar einem Dutzend (oder mehr) Filtern. Soll das etwa heißen, dass Sie einen FilterToBeanProxy für jeden einzel-nen Spring Security-Filter erstellen müssen? Nichts dergleichen. Es ist gewiss möglich, mehrere FilterToBeanProxys zur Datei web.xml hinzuzufügen (d. h. jeweils einen pro Spring Security-Filter), aber hierfür müsste man deutlich zu viel XML-Code schreiben. Um uns das Leben zu vereinfachen, bietet Spring Security einen kleinen Helfer für FilterToBeanProxy: FilterChainProxy. FilterChainProxy ist eine Implementierung von javax.servlet.Filter, die so konfi-guriert werden kann, dass sie mehrere Filter auf einmal miteinander verkettet. Abbildung 7.9 zeigt den Ansatz.

Spring-Anwendungskontext

Servlet-Kontext

FilterToBeanProxy FilterChainProxydelegiert an

Durch Spring

injizierter Filter #1

Durch Spring

injizierter Filter #2

Durch Spring

injizierter Filter #3

Abbildung 7.9 FilterChainProxy verkettet im Auftrag von FilterToBeanProxy mehrere Fil-ter miteinander.

FilterBeanProxy fängt die Clientanfrage ab und sendet sie zur Behandlung an Filter-ChainProxy. FilterChainProxy führt die Anfrage dann durch einen oder mehrere Filter, die im Spring-Anwendungskontext konfiguriert sind. FilterChainProxy wird im Spring-Anwendungskontext wie folgt konfiguriert:

<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy"> <property name="filterInvocationDefinitionSource"> <value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /**=filter1,filter2,filter3 </value> </property> </bean>

Die Eigenschaft filterInvocationDefinitionSource nimmt einen String entgegen, der dann in ein Schema konvertiert wird, das FilterChainProxy zur Verkettung von Filtern benutzt. Im vorliegenden Beispiel weist die erste Zeile FilterChainProxy an, die URL-Patterns in Kleinbuchstaben zu konvertieren, bevor der Vergleich vorgenommen

Page 46: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.4 Webanwendungen absichern

257

wird. Die nächste Zeile besagt, dass beim Deklarieren von URL-Patterns Pfade im Apache Ant-Stil verwendet werden sollen. Schließlich werden ein oder mehrere URL-Filter-Mappings vermittelt. Hier wird das Pat-tern /** (das in der Ant-Diktion alle URLs bezeichnet) auf drei Filter gemappt. Der als filter1-Bean konfigurierte Filter wird zum äußeren Filter und erhält die Anfrage zuerst. Es folgt die Bean filter2. filter3 schließlich ist die innere Bean und erhält die Anfrage zuletzt, bevor die eigentliche abgesicherte Ressource verarbeitet wird. Wenn eine Antwort zurückgegeben wird, durchläuft diese die umgekehrte Reihenfolge – von filter3 bis filter1.

Proxys für Spring Security konfigurieren Bislang haben wir die Konfiguration der Filter-Proxys weitgehend allgemein gehalten. Nun aber ist es an der Zeit, sie für den Einsatz in Spring Security zu konfigurieren. Zunächst wollen wir einen FilterToBeanProxy in web.xml konfigurieren:

<filter> <filter-name>Spring Security Filter Chain Proxy</filter-name> <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class> <init-param> <param-name>targetClass</param-name> <param-value>org.acegisecurity.util.FilterChainProxy</param-value> </init-param> </filter>

Hier haben wir FilterToBeanProxy so konfiguriert, dass für alle Beans des Typs Fil-terChainProxy im Spring-Kontext Proxys erstellt werden. Dies ist perfekt, da wir – wie Sie sich vielleicht schon gedacht haben – einen FilterChainProxy im Spring-Kontext konfigurieren. Bevor wir aber die Datei web.xml verlassen, müssen wir noch ein Filter-Mapping für Fil-terToBeanProxy konfigurieren:

<filter-mapping> <filter-name>Spring Security Filter Chain Proxy</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

Wie bei jedem <filter-mapping>-Servlet muss der Wert von <filter-name> dem <filter-name> des zu mappenden <filter>-Elements entsprechen. Was das <url-pattern> angeht, empfehlen wir /*, damit alle Anfragen durch Spring Security geschleift und ggf. abgesichert werden. Auch wenn es nicht notwendig ist, die gesamte Anwendung abzusichern, vereinfacht das Filtern aller Anfragen via /* die web.xml-Konfiguration. Wenn wir später FilterSecurityInterceptor konfigurieren, können wir auswählen, welche Teile der Anwendung gesichert werden sollen und welche nicht. So, das war’s. Mehr muss in der Datei web.xml nicht enthalten sein. Auch wenn Spring Security mehrere Filter zur Absicherung einer Webanwendung benutzt, müssen wir nur den einen FilterToBeanProxy-Filter in web.xml konfigurieren. Von hier an erfolgt die Konfiguration von Spring Security im Spring-Anwendungskontext.

Page 47: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

258

Apropos: Wir benötigen im Spring-Anwendungskontext eine FilterChainProxy-Bean, um von FilterToBeanProxy delegierte Anfragen zu behandeln. Bei der Anwendung RoadRantz beginnen wir mit der Spring Security-Minimalkonfiguration, indem wir einen FilterChainProxy im Spring-Anwendungskontext (in roadrantz-security.xml) mit der in Listing 7.1 gezeigten <bean>-Deklaration konfigurieren.

Listing 7.1 FilterChainProxy für Spring Security konfigurieren

<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy"> <property name="filterInvocationDefinitionSource"> <value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /**=httpSessionIntegrationFilter, authenticationProcessingFilter, exceptionTranslationFilter, filterSecurityInterceptor </value> </property> </bean>

Hier haben wir FilterChainProxy so konfiguriert, dass es vier Spring Security-Filter miteinander verkettet. Wir werden uns diese vier Filter gleich genauer ansehen. Zunächst ist es jedoch wichtig, darauf hinzuweisen, dass wir vier verschiedene <filter>-Einträge und acht verschiedene <filter-mapping>-Einträge in web.xml konfigurieren müssten, wenn es FilterChainProxy nicht gäbe. Dank FilterChainProxy wird die web.xml-Konfiguration auf ein einzelnes Paar aus <filter>- und <filter-mapping>-Elementen vereinfacht.

Abge-sicherte

Ressource

Kanalverarbeitung

Nebenläufige Session

HTTP-Session-Integration

Authentifizierungsverarbeitung

Verarbeitung gespeicherter Benutzerangaben

Anonymitätsverarbeitung

Abmeldung

Exception-Übersetzung

Sicherheits-Interceptor

Anfra

ge

Wenn die Anfrage des Clients eingetroffen ist und sich ihren Weg zur abgesicherten Res-source bahnt, passiert sie nacheinander alle Filter. Sie können sich die Filter von Spring Security wie die Häute einer Zwiebel vorstellen. Abbildung 7.10 zeigt, wie eine Anfrage auf dem Weg zur Ressource die einzelnen Spring Security-Filter durchläuft.

Abbildung 7.10 Die Spring Security-Filter sind schicht-weise angeordnet, um sicherheitstech-nische Aufgaben zu erledigen.

Page 48: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.4 Webanwendungen absichern

259

Abbildung 7.10 sieht aber nicht nur wie die Eröffnungssequenz eines Looney-Tunes-Trickfilms aus, sondern veranschaulicht auch etwas sehr Wichtiges in Zusammenhang mit Spring Security. Es sind nämlich nicht alle Spring Security-Filter für jede Anwendung er-forderlich, doch ist es unabdingbar, dass die eingesetzten in einer bestimmten Reihenfolge in der Eigenschaft filterInvocationDefinitionSource konfiguriert werden. Dies liegt daran, dass man bei einigen dieser Filter davon ausgeht, dass bestimmte Sicherheitsauf-gaben bereits erledigt wurden, bevor die Filter selbst in das Geschehen eingreifen. Wird die Reihenfolge der Filter vertauscht, dann treten Konflikte auf. Alle in der Eigenschaft filterInvocationDefinitionSource von FilterChainProxy konfigurierten Filter referenzieren ein <bean>-Element, das im Spring-Anwendungs-kontext konfiguriert wird. Folgen wir dem Pfad durch die Filter und beginnen mit dem Integrationsfilter.

7.4.2 Sicherheitskontexte behandeln

Haben Sie Findet Nemo gesehen? Wenn ja, dann wissen Sie bestimmt noch, dass einer der Protagonisten ein Doktorfisch namens Dorie ist und viele der lustigsten Momente in diesem Film dem Umstand geschuldet sind, dass Dorie mit ihrem Kurzzeitgedächtnis auf Kriegsfuß steht. Während des Films vergisst sie immer wieder bestimmte Tatsachen, so etwa das Ziel ihrer Reise oder den Namen von Marlin, dem Clownfisch, der nach seinem Sohn Nemo sucht. Wie sich herausstellt, haben HTTP und Dorie eine Menge gemeinsam. Sie wissen, dass HTTP ein zustandsloses Protokoll ist. Das bedeutet, dass HTTP – wie Dorie – im Hier und Jetzt lebt und zwischen den Anfragen zur Vergesslichkeit neigt. Hieraus ergibt sich ein kleines Problem für sichere Anwendungen, die über HTTP bereitgestellt werden: Wenn niemand HTTP daran erinnert, wer Sie sind, müssen Sie sich für jede Anfrage erneut bei der Anwendung anmelden. Zum Glück wurden diverse Lösungen ersonnen, um die Amnesie von HTTP in den Griff zu bekommen. Bei Java-basierten Webanwendungen können Sessions zur Speicherung von Daten zwischen den Anfragen verwendet werden. Für jede Anfrage lassen sich Benut-zerdaten aus der Session abrufen, zur Verarbeitung der Anfrage verwenden und dann wie-der in der Session ablegen, sodass sie für die nächste Anfrage zur Verfügung stehen. Der erste Spring Security-Filter, den eine Anfrage passieren muss, ist HttpSessionCon-textIntegrationFilter. Die Hauptaufgabe dieses Filters besteht darin, einen authenti-fizierten Benutzer zwischen den einzelnen Anfragen nicht zu vergessen. Der Filter wird wie folgt im Spring-Anwendungskontext konfiguriert:

<bean id="httpSessionIntegrationFilter" class="org.acegisecurity.context. HttpSessionContextIntegrationFilter"/>

Wenn eine Anfrage empfangen wird, kontrolliert HttpSessionContextIntegration-Filter, ob die Authentifizierungsdaten des betreffenden Benutzers in der Session gefun-den werden können (weil sie dort zuvor gespeichert wurden). Wenn ja, stellt HttpSes-

Page 49: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

260

sionContextIntegrationFilter diese Angaben zur Verfügung, damit Spring Security sie im Verlauf der aktuellen Anfrage verwenden kann. Am Ende der Anfrage legt Http-SessionContextIntegrationFilter die Authentifizierungsdaten des Benutzers wieder in der Session ab, sodass sie für die nächste Anfrage zur Verfügung stehen. Wenn HttpSessionContextIntegrationFilter die Authentifizierungsdaten eines Be-nutzers in der Session vorfindet, muss der Benutzer sich nicht noch einmal anmelden. Kann die Authentifizierung des Benutzers hingegen nicht gefunden werden, so bedeutet dies mit hoher Wahrscheinlichkeit, dass Letzterer sich noch nicht angemeldet hat. Um die Benutzeranmeldung zu behandeln, müssen wir einen authentifizierungsverarbeitenden Filter konfigurieren. Dies ist der nächste in FilterChainProxy enthaltene und von uns zu untersuchende Filter.

7.4.3 Aufforderung zur Benutzeranmeldung

Wenn Sie Webanwendungen mit Spring Security absichern, wird die Authentifizierung mithilfe eines Tags-Teams durchgeführt, das aus einem Eintrittspunkt für die Authentifi-zierung und einem Filter für die Authentifizierungsverarbeitung besteht. Wie Abbildung 7.11 zeigt, fordert ein Authentifizierungseintrittspunkt den Benutzer zur Eingabe seiner Anmeldeinformationen auf, die der authentifizierungsverarbeitende Filter verarbeitet.

Authentifizie-rungs-

eintrittspunkt

Authentifizierungs-verarbeitender

Filter

Aufforderungzur

Abmeldung

Abbildung 7.11 Authentifizierungseintrittspunkte und authentifizierungsverarbeitende Filter authentifi-zieren einen Webbenutzer gemeinsam.

Ein Authentifizierungseintrittspunkt startet den Anmeldevorgang, indem er dem Benutzer die Möglichkeit zur Eingabe seiner Anmeldeinformationen bietet. Macht der Benutzer die erforderlichen Angaben, dann versucht der authentifizierungsverarbeitende Filter, den Benutzer zu authentifizieren. Spring Security bietet fünf aufeinander abgestimmte Paare von Eintrittspunkten und Filtern zur Authentifizierung, die in Tabelle 7.5 aufgelistet sind. Anschließend wollen wir uns genauer ansehen, wie die Authentifizierung eines Benutzers durch die kombinierten Tätigkeiten von Authentifizierungseintrittspunkt und authentifizie-rungsverarbeitendem Filter ermöglicht wird. Dabei werden wir einige der Authentifizie-rungsoptionen von Spring Security untersuchen. Wir beginnen mit dem Spring Security-Support für die HTTP-Basisauthentifizierung.

Page 50: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.4 Webanwendungen absichern

261

Tabelle 7.5 Die Authentifizierungseintrittspunkte von Spring Security fordern den Benutzer zur Anmel-dung auf. Nach der Eingabe der Anmeldeinformationen verarbeitet ein authentifizierungsverarbeitender Filter die Anmeldungsanfrage.

Authentifizierungs- eintrittspunkt

Authentifizierungs- verarbeitender Filter

Zweck

BasicProcessingFilterEntry Point

BasicProcessingFilter Fordert den Benutzer über ein Browserdialog-feld auf, sich anzumelden. Verwendet die HTTP-Basisauthentifizierung.

AuthenticationProcessing FilterEntryPoint

AuthenticationProcessing Filter

Leitet den Benutzer auf eine auf einem HTML-Formular basierende Anmeldeseite um.

CasProcessingFilterEntry Point

CasProcessingFilter Leitet den Benutzer auf eine Anmeldeseite um, die von der Einzelanmeldungslösung CAS von JA-SIG vermittelt wird.

DigestProcessingFilterEntry Point

DigestProcessingFilter Fordert den Benutzer über ein Browserdialog-feld auf, sich anzumelden. Verwendet die HTTP-Digest-Authentifizierung.

X509ProcessingFilterEntry Point

X509ProcessingFilter Verarbeitet die Authentifizierung mit X.509-Zertifikaten.

Basisauthentifizierung Die einfachste Form der webbasierten Authentifizierung heißt Basisauthentifizierung. Die Basisauthentifizierung sendet eine HTTP-Antwort mit dem Code 401 („Nicht autorisiert“) an den Webbrowser. Wenn der Browser diese Antwort empfängt, bemerkt er, dass der Server eine Anmeldung des Benutzers verlangt. Daraufhin zeigt der Browser ein Dialog-feld an, in dem der Benutzer aufgefordert wird, einen Benutzernamen und ein Passwort einzugeben (siehe Abbildung 7.12).

Abbildung 7.12 Die HTTP-Basisauthentifizierung verwendet ein vom Browser ge-neriertes Anmeldedialogfeld, um den Benutzer zur Eingabe der Anmeldeinformationen aufzufor-dern. Das gezeigte Dialogfeld entstammt der Mac OS X-Version von Mozilla Firefox.

Sobald der Benutzer die Anmeldedaten eingegeben und die Versandschaltfläche angeklickt hat, sendet der Browser sie an den Server weiter, der für die Authentifizierung zuständig ist. War die Authentifizierung erfolgreich, so wird der Benutzer zum gewünschten Ziel-URL weitergeleitet. Andernfalls schickt der Server erneut eine HTTP-Antwort mit dem Code 401, woraufhin der Browser den Benutzer erneut zur Eingabe der Anmeldeinforma-tionen auffordert.

Page 51: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

262

Zur Verwendung der Basisauthentifizierung mit Spring Security muss zunächst eine BasicProcessingFilterEntryPoint-Bean konfiguriert werden:

<bean id="authenticationEntryPoint" class="org.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint"> <property name="realmName" value="RoadRantz" /> </bean>

BasicProcessingFilterEntryPoint hat nur eine zu konfigurierende Eigenschaft. Diese Eigenschaft realmName legt einen beliebigen String fest, der im Anmeldedialogfeld ange-zeigt wird. Dieser String soll dem Benutzer andeuten, was er eingeben soll, um sich anzu-melden. Das in Abbildung 7.12 gezeigte Dialogfeld etwa fordert den Benutzer zur Eingabe eines Benutzernamens und eines Passworts für den Bereich RoadRantz auf. Klickt der Benutzer die Schaltfläche OK im Dialogfeld an, dann werden Benutzername und Passwort via HTTP-Header an den Server zurückgegeben. Dort nimmt BasicProces-singFilter die Angaben entgegen und verarbeitet sie:

<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.basicauth.BasicProcessingFilter"> <property name="authenticationManager" ref="authenticationManager"/> <property name="authenticationEntryPoint" ref="authenticationEntryPoint"/> </bean>

BasicProcessingFilter entnimmt dem HTTP-Header Benutzername und Passwort und sendet sie an den Authentication-Manager weiter, der über die Eigenschaft authentica-tionManager verschaltet ist. Wenn die Authentifizierung erfolgreich war, wird ein Au-thentication-Objekt zur zukünftigen Bezugnahme in der Session abgelegt. Schlägt die Authentifizierung hingegen fehl, dann wird die Kontrolle an den Authentifizierungs-eintrittspunkt (d. h. den über die Eigenschaft authenticationEntryPoint verschalteten BasicProcessingFilterEntryPoint) übergeben, damit der Benutzer eine neue Chance erhält. Wiewohl für einfache Anwendungen ausreichend, weist die Basisauthentifizierung einige Einschränkungen auf. Zunächst einmal ist das vom Browser angezeigte Dialogfeld weder benutzerfreundlich noch optisch besonders ansprechend. Wenn Sie auf ein professionelles Auftreten Wert legen, ist die Basisauthentifizierung weniger gut geeignet. Für die Anwendung RoadRantz wollen wir eine gefällige Webseite verwenden, die Look und Feel der gesamten Anwendung aufgreift. Aus diesem Grund ist der Spring Security-Eintrittspunkt AuthenticationProcessingFilterEntryPoint für unsere Ansprüche besser geeignet. Wir wollen sehen, wie das funktioniert.

Formularbasierte Authentifizierung AuthenticationProcessingFilterEntryPoint ist ein Authentifizierungseintrittspunkt, der dem Benutzer ein HTML-basiertes Anmeldeformular anzeigt. Für unsere Anwendung RoadRantz konfigurieren wir sie wie folgt in der Datei roadrantz-security.xml:

Page 52: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.4 Webanwendungen absichern

263

<bean id="authenticationEntryPoint" class="org.acegisecurity.ui.webapp. AuthenticationProcessingFilterEntryPoint"> <property name="loginFormUrl" value="/login.htm" /> <property name="forceHttps" value="true" /> </bean>

AuthenticationProcessingFilterEntryPoint wird hier mit zwei Eigenschaften kon-figuriert: Die Eigenschaft loginFormUrl wird auf einen URL festgelegt, der die Anmelde-seite anzeigt (der URL wird relativ zum Kontext der Webanwendung angegeben). Die zweite Eigenschaft forceHttps hat den Wert true, damit die Anmeldeseite sicher über HTTPS angezeigt wird, und zwar auch dann, wenn die ursprüngliche Anfrage via HTTP übermittelt wurde. Hier haben wir loginFormUrl auf /login.htm festgelegt. loginFormUrl kann mit einem beliebigen URL konfiguriert werden, der den Benutzer zu einem HTML-Formular führt, über das er die Anmeldung durchführen kann. Im Falle der Anwendung RoadRantz ist /login.htm letztendlich mit einem UrlFilenameViewController aus Spring MVC ver-knüpft, der die Anmeldeseite anzeigt. Abbildung 7.13 zeigt, wie die Anmeldeseite von RoadRantz aussehen könnte.

Abbildung 7.13 Die Anmeldeseite von RoadRantz befindet sich auf /login.htm und wird vom UrlFi-lenameViewController aus Spring MVC behandelt.

Unabhängig von der Darstellungsform der Anmeldeseite ist es wichtig, dass diese ein HTML-Formular enthält, das etwa wie folgt aussieht:

Page 53: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

264

<form method="POST" action="j_acegi_security_check"> <b>Username: </b><input type="text" name="j_username"><br> <b>Password: </b><input type="password" name="j_password"><br> <input type="submit" value="Login"> </form>

Das Anmeldeformular muss zwei Felder namens j_username und j_password beinhal-ten, in die der Benutzer den Benutzernamen und das Passwort eingeben kann. Diese Felder werden von AuthenticationProcessingFilter erwartet. Das Attribut action des For-mulars wurde als j_acegi_security_check festgelegt; dieses wird von Authenticati-onProcessingFilter abgefangen. AuthenticationProcessingFilter ist ein Filter, der die Authentifizierung basierend auf den Angaben zu Benutzername und Passwort verarbeitet, die über die Parameter j_username und j_password übergeben wurden. Er wird in roadrantz-security.xml wie folgt konfiguriert:

<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter"> <property name="filterProcessesUrl" value="/j_acegi_security_check" /> <property name="authenticationFailureUrl" value="/login.htm?login_error=1" /> <property name="defaultTargetUrl" value="/" /> <property name="authenticationManager" ref="authenticationManager"/> </bean>

Die Eigenschaft filterProcessesUrl teilt dem AuthenticationProcessingFilter mit, welchen URL er abfangen soll. Hierbei handelt es sich um den URL, der auch Bestandteil des Attributs action des Anmeldeformulars ist. Vorgegeben ist /j_acegi_ security_check, aber ich habe ihn hier explizit definiert, um zu verdeutlichen, dass Sie ihn bei Bedarf auch ändern können. Die Eigenschaft authenticationFailureUrl gibt an, wohin der Benutzer weitergeleitet wird, wenn die Authentifizierung fehlschlagen sollte. In unserem Fall gelangt er erneut auf die Anmeldeseite, wobei ein Parameter übergeben wird, der signalisiert, dass die Authenti-fizierung fehlgeschlagen ist (damit etwa eine Fehlermeldung angezeigt werden kann). Unter normalen Umständen – d. h. wenn die Authentifizierung erfolgreich ist – legt Au-thenticationProcessingFilter ein Authentication-Objekt in der Session ab und leitet den Benutzer zur gewünschten Zielseite weiter. Die Eigenschaft defaultTargetUrl definiert, was in dem unwahrscheinlichen Fall geschieht, dass der Ziel-URL nicht bekannt ist. Dies könnte beispielsweise geschehen, wenn der Benutzer den Anmeldebildschirm direkt aufruft, ohne zuvor einen Zugriffsversuch auf eine abgesicherte Ressource durch-geführt zu haben. Schließlich wird die Eigenschaft authenticationManager mit einem Verweis auf eine authenticationManager-Bean verschaltet. Wie alle anderen authentifizierungsverarbei-tenden Filter ist der formularbasierte AuthenticationProcessingFilter auf einen Authentication-Manager angewiesen, der die Identität des Benutzers verifiziert. Wir haben nun in der Spring-Konfiguration einen authentifizierungsverarbeitenden Filter und einen Authentifizierungseintrittspunkt definiert, um Benutzern die Anmeldung zu er-

Page 54: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.4 Webanwendungen absichern

265

möglichen. Aber eine Sache ist noch zu erledigen. Während der Filter für die Authentifi-zierungsverarbeitung im FilterChainProxy verschaltet ist, fragen Sie sich wahrschein-lich, was mit dem Authentifizierungseintrittspunkt geschehen soll. Welcher Teil von Spring Security verwendet den Authentifizierungseintrittspunkt, um den Benutzer zur Anmeldung aufzufordern? Diese Frage werden wir bald beantworten. Zuvor sehen wir uns jedoch den Exception-Übersetzungsfilter an.

7.4.4 Sicherheits-Exceptions behandeln

Alle Spring Security-Filter, die zur Sicherheitsimplementierung eingesetzt werden, können eine Variante von AuthenticationException oder AccessDeniedException auslösen. Eine AuthenticationException beispielsweise wird ausgelöst, wenn der Benutzer aus irgendeinem Grund nicht authentifiziert werden kann. Dies ist etwa der Fall, wenn der Be-nutzer eine ungültige Kombination aus Benutzername und Passwort übergeben hat. Eine andere Möglichkeit wäre auch, dass der Benutzer nicht einmal versucht hat, sich anzumel-den. Doch auch nach einer erfolgreichen Authentifizierung kann es sein, dass der Benutzer nicht die für den Besuch bestimmter abgesicherter Seiten erforderlichen Berechtigungen erhält. In diesem Fall wird AccessDeniedException ausgelöst. Werden AuthenticationException und AccessDeniedException nicht auf irgendeine Weise behandelt, dann durchlaufen sie den Servlet-Container und werden im Browser als sehr unschöner Stack-Trace angezeigt. Dies ist selbstverständlich alles andere als ideal. Eine kontrolliertere Form der Behandlung solcher Exceptions ist zu bevorzugen. Hier nun kommt ExceptionTranslationFilter ins Spiel. ExceptionTranslation-Filter wird auf einer Ebene außerhalb von FilterSecurityInterceptor konfiguriert, hat also die Möglichkeit, von FilterSecurityInterceptor ausgelöste Exceptions abzu-fangen. ExceptionTranslationFilter ist in Spring wie folgt deklariert:

<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter"> <property name="authenticationEntryPoint" ref="authenticationEntryPoint" /> </bean>

ExceptionTranslationFilter fängt die von FilterSecurityInterceptor ausgelös-ten Exceptions ab – so weit, so gut. Aber was geschieht dann mit ihnen? Beachten Sie, dass ExceptionTranslationFilter eine Referenz auf den Authentifizie-rungseintrittspunkt injiziert wird. Wenn ExceptionTranslationFilter eine Authenti-cationException abfängt, bedeutet dies, dass der Benutzer nicht erfolgreich authentifi-ziert werden konnte. In diesem Fall wird dem Benutzer der in der Eigenschaft authenti-cationEntryPoint konfigurierte Authentifizierungseintrittspunkt übermittelt, damit er einen neuen Versuch starten kann.

Page 55: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

266

Umgang mit Autorisierungs-Exceptions Eine AccessDeniedException zeigt an, dass der Benutzer zwar authentifiziert werden konnte, die für den Zugriff auf die angeforderte Ressource erforderlichen Berechtigungen jedoch nicht erhalten hat. In diesem Fall wird ein HTTP-Fehler 403 („Verboten“) an den Browser zurückgegeben, der angibt, dass der Benutzer auf die Ressource nicht zugreifen darf. Standardmäßig verwendet ExceptionTranslationFilter für die Behandlung von AccessDeniedExceptions ein AccessDeniedHandlerImpl. Sofern nicht anders konfigu-riert, sendet AccessDeniedHandlerImpl lediglich den HTTP-Fehler 403 an den Browser. Leider wird dieser Fehler im Browser normalerweise auf eine höchst benutzerunfreund-liche Art angezeigt. Wir können jedoch eine eigene AccessDeniedHandlerImpl konfigurieren, die den Be-nutzer an eine ansprechender formatierte Fehlerseite weiterleitet, wenn eine AccessDe-niedException abgefangen wird. Der folgende XML-Code konfiguriert eine AccessDe-niedHandlerImpl, die den Benutzer an eine Fehlerseite unter der Adresse /error.htm weiterleitet:

<bean id="accessDeniedHandler" class="org.acegisecurity.ui.AccessDeniedHandlerImpl"> <property name="errorPage" value="/error.htm" /> </bean>

Nun muss lediglich noch dieser accessDeniedHandler im ExceptionTranslationFil-ter verschaltet werden:

<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter"> <property name="authenticationEntryPoint" ref="authenticationEntryPoint" /> <property name="accessDeniedHandler" ref="accessDeniedHandler" /> </bean>

Mittlerweile haben wir drei der vier Spring Security-Sicherheitsfilter verschaltet. Die bis-lang konfigurierten drei Filter sind die Sicherheitsstifte im Spring Security-Schloss. Ab-schließend wollen wir mit FilterSecurityInterceptor den dazugehörigen Riegel kon-figurieren, der festlegt, ob der Zugriff auf eine Webressource erlaubt wird oder nicht.

7.4.5 Websicherheit erzwingen

Eine von einem Benutzer angeforderte Seite in einer Webanwendung ist entweder sicher-heitskritisch oder aber nicht. In Spring Security fängt ein filterbasierter Sicherheits-Interceptor die Anfragen ab, stellt fest, ob die Anfrage sicher ist, und bietet dem Authenti-cation- und Access-Decision-Manager die Möglichkeit, Identität und Berechtigungen des Benutzers zu verifizieren. Dies wird in der Spring-Konfigurationsdatei wie folgt deklariert:

<bean id="filterSecurityInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor"> <property name="authenticationManager" ref="authenticationManager" />

Page 56: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.4 Webanwendungen absichern

267

<property name="accessDecisionManager" ref="accessDecisionManager" /> <property name="objectDefinitionSource"> <value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /editProfile.htm=ROLE_MOTORIST </value> </property> </bean>

FilterSecurityInterceptor spielt die Rolle des Sicherheits-Interceptors (siehe Ab-schnitt 7.1.1) für Webanwendungen. Wenn eine Ressource angefordert wird (also eine Webseite oder ein Spring MVC-Controller), führt FilterSecurityInterceptor ver-schiedene Prüfungen durch, um festzustellen, ob der Benutzer auf die Ressource zugreifen darf:

Wurde der Benutzer authentifiziert? Wenn nicht, löst FilterSecurityInterceptor eine AuthenticationException aus, die der Exception-Übersetzungsfilter behandelt.

Ist die angeforderte Ressource abgesichert? Die Eigenschaft objectDefinitionSour-ce definiert, welche Ressourcen geschützt und welche Berechtigungen erforderlich sind, um auf sie zuzugreifen. Wenn der URL der Anfrage einem der URL-Patterns in objectDefinitionSource entspricht, ist die Ressource sicher.

Wurden dem Benutzer die für den Zugriff auf die Ressource erforderlichen Berechti-gungen gewährt? FilterSecurityInterceptor vergleicht die dem Benutzer gewähr-ten Berechtigungen mit denen, die als für die Ressource erforderlich deklariert sind. Wenn die Berechtigungen des Benutzers ausreichen, wird der Zugriff gewährt. Andern-falls löst FilterSecurityInterceptor eine AccessDeniedException aus, die der Exception-Übersetzungsfilter behandelt.

FilterSecurityInterceptor arbeitet bei der Entscheidungsfindung aber nicht allein. Aus diesem Grund wird er mit einer Referenz auf einen Authentication-Manager und einer Referenz auf einen Access-Decision-Manager verschaltet. Über die Eigenschaft objectDefinitionSource deklarieren wir, welche Ressourcen abgesichert werden und welche Berechtigungen für den Zugriff darauf erforderlich sind. Die erste Zeile gibt an, dass alle URL-Patterns vor dem Vergleich auf Kleinbuchstaben umgesetzt werden sollen (andernfalls wird beim Vergleich die Groß-/Kleinschreibung un-terschieden). Die nächste Zeile legt fest, dass wir zur Deklaration der URL-Patterns Pfade im Apache Ant-Stil verwenden. Beginnend bei der dritten Zeile, deklarieren wir mindestens ein URL-Pattern und die für den Zugriff jeweils erforderlichen Berechtigungen. In diesem Fall gibt es ein URL-Pattern, das sicherstellt, dass nur authentifizierte Benutzer mit der Rolle ROLE_MOTORIST die Seite editProfile.htm besuchen dürfen. Wenn der Benutzer authentifiziert wurde und über ausreichende Berechtigungen verfügt, lässt FilterSecurityInterceptor die Anfrage weiterlaufen. Sollte FilterSecurity-Interceptor jedoch feststellen, dass der Benutzer nicht über die erforderlichen Berechti-gungen verfügt, dann wird entweder eine AuthenticationException oder eine Access-DeniedException ausgelöst.

Page 57: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

268

Bis hierher haben wir die zur Absicherung der Anwendung RoadRantz wesentlichen Spring Security-Filter konfiguriert. Es gibt aber noch einen Filter, der zwar nicht obligato-risch, aber manchmal recht praktisch ist, um sicherzustellen, dass sensible Daten in Web-anfragen sicher übertragen werden. Als Nächstes zeige ich Ihnen, wie Sie sicherstellen, dass sichere Anfragen mit dem Spring Security-Filter ChannelProcessingFilter über HTTPS übertragen werden.

7.4.6 Sicheren Kanal schützen

Der Buchstabe S ist der wichtigste im ganzen Internet. Jeder, der in seinem Leben länger als fünf Minuten im Web gesurft ist, weiß, dass die meisten Webseiten mit URLs ver-knüpft sind, die mit http:// beginnen. Das liegt daran, dass die meisten Webseiten über das HTTP-Protokoll angefordert und gesendet werden. HTTP ist für die meisten Seiten ausreichend, zur Übertragung vertraulicher Daten über das Internet jedoch absolut ungeeignet. Über HTTP gesendete Daten lassen sich von hinter-hältigen Hackern leicht abfangen und für eigene niederträchtige Zwecke verwenden. Wenn Daten vertraulich gesendet werden sollen, kommt der Buchstabe S ins Spiel. Bei derartigen Seiten werden Sie feststellen, dass der URL mit https:// statt einfach mit http:// beginnt. Bei HTTPS werden die Daten zwar via HTTP gesendet, jedoch über einen ande-ren Port. Dabei werden sie zudem verschlüsselt, sodass sie zwar unter Umständen abge-fangen, aber von Unbefugten nicht gelesen werden können. Ein Problem im Zusammenhang mit HTTPS besteht darin, dass die Verantwortung dafür, dass eine Seite via HTTPS übertragen wird, auf demjenigen lastet, der den Link auf die sichere Seite schreibt. Anders ausgedrückt: Der URL einer Seite, die via verschlüsseltem HTTPS geschützt wird, muss so verknüpft werden, dass er mit https:// beginnt. Fehlt das s, so wird die Seite unverschlüsselt via HTTP versendet. Da es nur allzu leicht ist, das unentbehrliche s wegzulassen, bietet Spring Security eine narrensichere Methode, um sicherzustellen, dass bestimmte Seiten in jedem Fall via HTTPS übertragen werden – unabhängig vom verknüpfenden URL. Wie Abbildung 7.14 zeigt, ist ChannelProcessingFilter ein Spring Security-Filter, der eine Anfrage abfängt, überprüft, ob sie sicher sein muss, und sie ggf. an eine HTTPS-Variante des ursprüng-lichen URL umleitet.

Web-

AnwendungWebbrowser

HTTP-Anfrage

HTTPS

Ka

na

lve

rarb

eitu

ng

sfilte

r

Abbildung 7.14 ChannelProcessingFilter leitet HTTP-Anfragen als HTTPS (und umgekehrt) weiter und gewährleistet so die für die Anfrage jeweilig erforderliche Sicherheitsstufe.

Page 58: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.4 Webanwendungen absichern

269

In roadrantz-security.xml haben wir einen ChannelProcessingFilter für die Anwen-dung RoadRantz wie folgt konfiguriert:

<bean id="channelProcessingFilter" class="org.acegisecurity.securechannel.ChannelProcessingFilter"> <property name="filterInvocationDefinitionSource"> <value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /login.htm=REQUIRES_SECURE_CHANNEL /j_acegi_security_check*=REQUIRES_SECURE_CHANNEL /**=REQUIRES_INSECURE_CHANNEL </value> </property> <property name="channelDecisionManager" ref="channelDecisionManager" /> </bean>

Die Eigenschaft filterInvocationDefinitionSource wird so konfiguriert, dass der ChannelProcessingFilter ihr entnehmen kann, welche Seiten mit HTTPS geschützt werden sollen und welche nicht. Sie wird mit einem oder mehreren URL-Patterns konfigu-riert, die als sicher bzw. als nicht sicher gekennzeichnet werden. Bevor die URL-Patterns jedoch in Erscheinung treten, müssen wir einige Grundregeln für die Behandlung von URLs festlegen. Die erste Zeile gibt an, dass Spring Security alle URLs normalisieren soll (CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON), bevor sie mit den nachfolgenden URL-Patterns verglichen werden. Die zweite Zeile enthält PAT-TERN_TYPE_APACHE_ANT, um zum Ausdruck zu bringen, dass die angegebenen URL-Patterns unter Verwendung von Pfaden im Apache Ant-Stil dargestellt werden. Alle nachfolgenden Zeilen mappen jeweils ein URL-Pattern auf die entsprechenden Sicherheitsbedingungen. In der Anwendung RoadRantz muss die Anmeldeseite sicher sein (damit niemand das Passwort eines Benutzers abfangen kann). Aus diesem Grund wird /login.htm mit REQUIRES_SECURE_CHANNEL verknüpft, um anzuzeigen, dass die Seite via HTTPS versendet werden soll. Ähnlich müssen auch an den URL gesendete Daten ver-schlüsselt werden. Wie Sie in Kürze erfahren werden, reagiert der Spring Security-Filter AuthenticationProcessingFilter auf /j_acegi_security_check, weswegen dieses URL-Pattern ebenfalls auf REQUIRES_SECURE_CHANNEL festgelegt wird. Alle anderen Seiten in der Anwendung RoadRantz erfordern keine Verschlüsselung. Aus diesem Grund wird das Pattern /** (das in der Ant-Pfadsyntax die Bedeutung „alle URLs“ hat) mit REQUIRES_INSECURE_CHANNEL verknüpft; auf diese Weise legt man fest, dass alle anderen Seiten über das einfache, unsichere HTTP gesendet werden müssen. Beachten Sie, dass diese Seiten einen unsicheren Kanal verwenden müssen! Das bedeutet, dass Chan-nelProcessingFilter die Anfrage auf das HTTP-Gegenstück umsetzt, wenn ein Zugriff auf eine dieser Seiten über HTTPS erfolgt.

Kanalentscheidungen verwalten Zwar übernimmt ChannelProcessingFilter die Aufgabe, HTTP-Anfragen auf HTTPS umzuleiten (und umgekehrt), doch muss nicht unbedingt jede Anfrage umgeleitet werden. Insofern verwendet ChannelProcessingFilter eine (in der Eigenschaft channelDeci-

Page 59: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

270

sionManager verschaltete) ChannelDecisionManagerImpl, um festzustellen, ob eine Anfrage umgeleitet werden muss oder nicht. Die ChannelDecisionManagerImpl wird wie folgt konfiguriert:

<bean id="channelDecisionManager" class="org.acegisecurity.securechannel.ChannelDecisionManagerImpl"> <property name="channelProcessors"> <list> <bean class="org.acegisecurity.securechannel.SecureChannelProcessor"/> <bean class="org.acegisecurity.securechannel.InsecureChannelProcessor"/> </list> </property> </bean>

Hier haben wir ChannelDecisionManagerImpl mit zwei Kanalprozessoren konfiguriert: einem für die Verarbeitung sicherer Kanäle (HTTPS) und einem für unsichere Kanäle (HTTP). Bevor wir unsere Abhandlung zum Spring Security-Support für webbasierte Sicherheit be-enden, wollen wir noch untersuchen, wie man die Tag-Bibliothek von Spring Security verwendet, um Sicherheitsregeln innerhalb einer Seite einer Webanwendung zu erzwingen.

7.5 Sicherheit für die Darstellungsebene

In den meisten Anwendungen gibt es gewisse Elemente, die nur Benutzer bestimmter Kategorien sehen dürfen. Wie Sie bereits gesehen haben, verhindern die Filter in Spring Security, dass bestimmte Seiten Benutzern angezeigt werden, denen erforderliche Berech-tigungen nicht erteilt wurden. Filter ermöglichen allerdings nur ein grob konfigurierbares Maß an Sicherheit, da sie den Zugriff auf der Anfrageebene verhindern. In manchen Fällen benötigen Sie daher eine fei-ner abgestufte Kontrolle darüber, was der Benutzer sehen darf. So kann es sein, dass alle Benutzer einer Anwendung eine bestimmte Seite anzeigen können, aber nur Benutzer mit erforderlichen Rechten bestimmte Elemente auf dieser Seite sehen dürfen. Um in Webanwendungen feinere Abstufungen bei der Kontrolle zu erzielen, bietet Spring Security eine kleine, aber feine Bibliothek mit JSP-Tags. Diese Tag-Bibliothek enthält nur drei Tags, die in Tabelle 7.6 aufgeführt sind.

Tabelle 7.6 In Spring Security enthaltene JSP-Tags für die Sicherheit auf der Darstellungsebene

Tag-Name Zweck

<authz:acl> Rendert den Tag-Inhalt, sofern der Benutzer über bestimmte Berechtigungen für ein Domänenobjekt verfügt.

<authz:authentication> Zeigt Benutzerdaten an.

<authz:authorize> Rendert den Tag-Inhalt, sofern dem Benutzer bestimmte Berechtigungen gewährt (oder nicht gewährt) wurden.

Page 60: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.5 Sicherheit für die Darstellungsebene

271

Um diese Tags in einer JSP-Seite zu verwenden, muss die Tag-Bibliothek mit der JSP-Anweisung <%@taglib%> importiert werden:

<%@ taglib prefix="authz" uri="http://acegisecurity.org/authz" %>

Wie wollen uns ansehen, wie diese Tags eingesetzt werden. Beginnen wir mit <authz:authorize>.

7.5.1 Bedingtes Inhalts-Rendering

Das nützlichste JSP-Tag von Spring Security ist <authz:authorize>. Dieses Tag führt eigentlich eine if-Anweisung aus und wertet dabei aus, ob der aktuelle Benutzer aus-reichende Berechtigungen genießt, um bestimmte Inhalte anzeigen zu können. Ist dies der Fall, so wird der Tag-Inhalt gerendert, andernfalls wird er ignoriert. Um dies zu veranschaulichen, wollen wir der Anwendung RoadRantz eine Begrüßungs-nachricht und einen Link zur Abmeldung hinzufügen. Es ist wenig sinnvoll, einen nicht authentifizierten Benutzer willkommen zu heißen – und noch weniger zweckmäßig, ihm einen Link zur Abmeldung anzuzeigen. Aus diesem Grund wollen wir sicherstellen, dass dem Benutzer bestimmte Berechtigungen gewährt wurden, bevor diese Informationen angezeigt werden. Mithilfe des Attributs ifAllGranted des Tags <authz:authorize> können wir den anzuzeigenden Inhalt mit folgendem JSP-Ausschnitt ergänzen:

<authz:authorize ifAllGranted="ROLE_MOTORIST,ROLE_VIP"> Welcome Motorist!<br/> <a href="j_acegi_logout">Logoff</a> </authz:authorize>

Da das Attribut ifAllGranted verwendet wurde, werden die Inhalte des Tags nur dann gerendert, wenn dem Fahrer die Berechtigungen ROLE_MOTORIST und ROLE_VIP gewährt wurden. Allerdings ist diese zu restriktiv, weil zwar allen Fahrern die Rolle ROLE_ MOTORIST, jedoch nur ein paar wenigen die Rolle ROLE_VIP zugewiesen wurde. Insofern wäre das Attribut ifAnyGranted wohl eher angebracht:

<authz:authorize ifAnyGranted="ROLE_MOTORIST,ROLE_VIP"> Welcome Motorist!<br/> <a href="j_acegi_logout">Logoff</a> </authz:authorize>

In diesem Fall muss dem Benutzer die Rolle ROLE_MOTORIST oder die Rolle ROLE_VIP zugewiesen sein, damit Begrüßung und Abmeldemöglichkeit angezeigt werden. Wiewohl naheliegend, soll doch ausdrücklich angemerkt werden, dass, wenn Sie nur auf eine einzelne Berechtigung prüfen, die Wahl zwischen ifAllGranted und ifAnyGranted akademisch ist: Beide Attribute führen – sofern der Wert nur eine Berechtigung umfasst – zum selben Ergebnis. Das letzte Attribut heißt ifNotGranted. Hierbei werden die Inhalte nur dargestellt, wenn dem Benutzer eine oder mehrere bestimmte Berechtigungen nicht gewährt wurden. Den folgenden Ansatz kann man beispielsweise verwenden, um zu verhindern, dass anonymen Benutzern Inhalte angezeigt werden.

Page 61: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

272

<authz:authorize ifNotGranted="ROLE_ANONYMOUS"> <p>This is super-secret content that anonymous users aren't allowed to see.</p> </authz:authorize>

Diese drei Attribute decken für sich genommen schon sehr viele Möglichkeiten ab. Doch werden sie gemeinsam verwendet, dann lässt sich damit so mancher sicherheitstechnische Trick heraufbeschwören. Beim Einsatz in Kombination werden die Attribute durch logi-sche UND-Verknüpfung ausgewertet. Betrachten Sie etwa einmal Folgendes:

<authz:authorize ifAllGranted="ROLE_MOTORIST" ifAnyGranted="ROLE_VIP,ROLE_FAST_LANE" ifNotGranted="ROLE_ADMIN"> <p>Only special users see this content.</p> </authz:authorize>

Bei einer solchen Festlegung wird der Inhalt des Tags nur dargestellt, wenn dem Benutzer die Rolle ROLE_MOTORIST sowie entweder die Rolle ROLE_VIP oder die Rolle ROLE_ FAST_LANE zugeordnet wurde – und nicht die Rolle ROLE_ADMIN. Zwar ist dieses Beispiel recht konstruiert, aber Sie können sich sicher vorstellen, wie leistungsfähig das Tag <authz:authorize> sein kann, wenn man die drei Attribute gemeinsam verwendet. Zu kontrollieren, was der Benutzer sehen kann, ist nur eine Facette der JSP-Tag-Bibliothek von Spring Security. Wenden wir uns nun den Spring Security-Tags zu, mit denen sich Authentifizierungsdaten zu einem Benutzer darstellen lassen.

7.5.2 Authentifizierungsdaten anzeigen

In den vorangegangenen Abschnitten haben wir eine Begrüßung zur Anwendung RoadRantz hinzugefügt, die autorisierten Benutzern angezeigt wird. Der Einfachheit halber lautet die Begrüßung schlicht „Welcome Motorists!“. Das ist ein guter Ausgangspunkt, aber wir wollen die Anwendung etwas stärker personalisieren, indem wir statt Motorists den Anmeldenamen des Benutzers anzeigen. Zum Glück ist dieser normalerweise in dem Objekt enthalten, welches von der Methode Authentication.getPrincipal() für den Benutzer zurückgegeben wird. Jetzt brauchen wir nur noch eine praktische Möglichkeit, um in JSP auf das Prinzipalobjekt zuzugreifen. Diese bietet uns das <authz:authentication>-Tag. Das Tag <authz:authentication> rendert Eigenschaften des von Authentication. getPrincipal() zurückgegebenen Objekts in eine JSP-Ausgabe. Authentication.get-Principal() gibt gewöhnlich eine Implementierung des Spring Security-Interfaces org.acegisecurity.userdetails.UserDetails zurück, die auch eine Methode get-Username() enthält. Deswegen müssen wir lediglich das folgende <authz:authen-tication>-Tag ergänzen, um die Eigenschaft username des UserDetails-Objekts anzu-zeigen:

<authz:authorize ifAnyGranted="ROLE_MOTORIST,ROLE_VIP"> Welcome <authz:authentication operation="username"/> </authz:authorize>

Das Attribut operation ist ein wenig irreführend, da es suggeriert, sein Zweck bestünde darin, eine Methode aufzurufen. Das stimmt zwar im Wesentlichen, doch handelt es sich

Page 62: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.6 Methodenaufrufe schützen

273

dabei um die Anfragemethode der Eigenschaft, deren Name im Attribut operation ange-geben ist. Um den Namen der aufzurufenden Methode zu generieren, wird standardmäßig der erste Buchstabe des Wertes von operation in einen Großbuchstaben umgewandelt und dem Ergebnis dann get vorangestellt. In diesem Fall wird die Methode getUsername() aufge-rufen und der Rückgabewert in eine JSP-Ausgabe gerendert. Sie haben nun gesehen, wie man Webanwendungen mit den Spring Security-Filtern absi-chert. Bevor wir das Kapitel Spring Security abschließen, werfen wir noch einen kurzen Blick auf die Frage, wie man Methodenaufrufe mit Spring Security und AOP schützt.

7.6 Methodenaufrufe schützen

Während Spring Security zur Absicherung von Webanfragen Servlet-Filter verwendet, nutzt es den AOP-Support von Spring, um deklarative Sicherheit auf Methodenebene zu bieten. Sie richten also keinen SecurityEnforcementFilter zur Sicherheitserzwingung ein, sondern einen Spring AOP-Proxy, der Methodenaufrufe abfängt und die Kontrolle an einen Sicherheits-Interceptor übergibt.

7.6.1 Sicherheitsaspekt erstellen

Die wahrscheinlich einfachste Möglichkeit, einen AOP-Proxy einzurichten, besteht in der Verwendung von Springs BeanNameAutoProxyCreator und der einfachen Auflistung der zu sichernden Beans.4 Nehmen wir beispielsweise an, Sie wollen die Beans courseServi-ce und billingService sichern:

<bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="interceptorNames"> <list> <value>securityInterceptor</value> </list> </property> <property name="beanNames"> <list> <value>courseService</value> <value>billingService</value> </list> </property> </bean>

Hier wurde der Autoproxy-Ersteller angewiesen, seine Beans mit einem einzelnen Inter-ceptor zu erstellen: einer Bean namens securityInterceptor. Die securityIntercep-tor-Bean wird wie folgt konfiguriert:

4 Dies ist nur ein Vorschlag. Wenn Sie einen der anderen Ansätze zur Proxy-Bildung für Beans

bevorzugen – z. B. ProxyFactoryBean oder DefaultAdvisorAutoProxyCreator – können Sie dies gerne tun.

Page 63: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

274

<bean id="securityInterceptor" class="org.acegisecurity.intercept.method.MethodSecurityInterceptor"> <property name="authenticationManager"> <ref bean="authenticationManager"/> </property> <property name="accessDecisionManager"> <ref bean="accessDecisionManager"/> </property> <property name="objectDefinitionSource"> <value> com.springinaction.springtraining.service. CourseService.createCourse=ROLE_ADMIN com.springinaction.springtraining.service. CourseService.enroll*=ROLE_ADMIN,ROLE_REGISTRAR </value> </property> </bean>

MethodSecurityInterceptor ist für Methodenaufrufe das, was FilterSecurity-Interceptor für Servlet-Anfragen darstellt: Er fängt den Aufruf ab und koordiniert die Anstrengungen des Authentication-Managers und des Access-Decision-Managers, um si-cherzustellen, dass die Ansprüche der Methode erfüllt werden. Beachten Sie, dass die Eigenschaften authenticationManager und accessDecision-Manager ebenso vorhanden sind wie bei FilterSecurityInterceptor. Und tatsächlich verschalten Sie dieselben Beans in diesen Eigenschaften wie bei FilterSecurityInter-ceptor. MethodSecurityInterceptor verfügt zudem ebenso wie FilterSecurityInterceptor über eine Eigenschaft objectDefinitionSource. Doch auch wenn diese hier den glei-chen Zweck erfüllt wie bei FilterSecurityInterceptor, wird sie etwas anders konfigu-riert. Statt URL-Patterns mit Berechtigungen zu verknüpfen, kombiniert diese Eigenschaft Methoden-Patterns mit Berechtigungen, die zum Aufrufen der Methode erforderlich sind. Ein Methoden-Pattern (Abbildung 7.15) enthält den vollqualifizierten Klassennamen und den Methodennamen der Methode(n), die geschützt werden soll(en). Beachten Sie, dass Sie entweder am Anfang oder am Ende eines Methoden-Patterns Platzhalter einsetzen kön-nen, um eine Übereinstimmung mit mehreren Methoden zu erzielen.

com.springinaction...CourseService.enroll* =ROLE_ADMIN,ROLE_REGISTRAR

PlatzhalterVollqualifizierter Klassenname

Erforderliche Berechtigungen

Abbildung 7.15 Sicherheitsregeln für Methoden werden definiert, indem ein vollquali-fizierter Klassenname und ein Metho-denname auf die Berechtigungen gemappt werden, die zur Ausführung dieser Methode erforderlich sind. Bei Angabe der Methode können Sie Platzhalter verwenden.

Wenn eine abgesicherte Methode aufgerufen wird, bestimmt der MethodSecurityInter-ceptor, ob der Benutzer authentifiziert wurde und die für den Aufruf der Methode erfor-derlichen Berechtigungen erhalten hat. Ist dies der Fall, dann folgt der Aufruf der Ziel-methode, ansonsten wird eine AcegiSecurityException ausgelöst. Genauer: Eine Au-thenticationException wird ausgelöst, wenn der Benutzer nicht authentifiziert werden

Page 64: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7.6 Methodenaufrufe schützen

275

kann, während eine AccessDeniedException ausgelöst wird, wenn der Benutzer nicht die für den Aufruf erforderlichen Berechtigungen genießt. Entsprechend der Exception-Philosophie in Spring ist AcegiSecurityException eine ungeprüfte Exception. Der aufrufende Code kann die Exception also entweder abfangen oder ignorieren. Das Schreiben von Methodensicherheitsattributen in die Spring-Konfigurationsdatei ist nur eine Möglichkeit, Sicherheit auf der Methodenebene zu deklarieren. Betrachten wir nun, wie man dies mithilfe von Jakarta Commons Attributes tut.

7.6.2 Methoden mit Metadaten absichern

Was Transaktionen und Handler-Mappings betrifft, müssen Sie zuallererst eine Metadaten-implementierung deklarieren, damit Spring weiß, wie Metadaten geladen werden müssen. Wenn Sie Ihrem Anwendungskontext noch keine CommonsAttributes-Bean hinzugefügt haben, holen Sie dies jetzt nach:

<bean id="attributes" class="org.springframework.metadata.commons.CommonsAttributes"/>

Als Nächstes müssen Sie eine Objektdefinitionsquelle deklarieren. In Abschnitt 7.6.1 taten wir dies, indem wir die Eigenschaft objectDefinitionSource mit einem String festleg-ten, der Sicherheitsattribute auf Methoden mappt. Diesmal jedoch werden wir Sicherheits-attribute direkt im Quellcode des abzusichernden Objekts deklarieren. MethodDefinitio-nAttributes aus Spring Security ist eine Objektdefinitionsquelle, die ihre Sicherheits-attribute den Metadaten des abzusichernden Objekts entnimmt:

<bean id="objectDefinitionSource" class="org.acegisecurity.intercept.method.MethodDefinitionAttributes"> <property name="attributes"><ref bean="attributes"/></property> </bean>

Die Eigenschaft attributes von MethodDefinitionAttributes wird mit einer Refe-renz auf die attributes-Bean verschaltet, damit sie weiß, dass sie Sicherheitsattribute mithilfe von Jakarta Commons Attributes abrufen muss.5 Nach erfolgter Konfiguration von objectDefinitionSource verschalten wir sie in der Eigenschaft objectDefinitionSource von MethodSecurityInterceptor (und ersetzen so die String-Definition aus Abschnitt 7.6.1):

<bean id="securityInterceptor" class="org.acegisecurity.intercept.method.MethodSecurityInterceptor"> … <property name="objectDefinitionSource"> <ref bean="objectDefinitionSource"/> </property> </bean>

5 Wenn Spring einmal JSR-175-Annotationen unterstützt, werden Sie die Eigenschaft attributes mit

einer anderen Metadatenimplementierung verschalten müssen.

Page 65: Craig Walls, Ryan Breidenbach Spring im Einsatz · stellen (Lucene, Log4J). Sie alle aber waren nicht in der Lage, die grundlegenden Bedürf-nisse von Unternehmensanwendungen zu erfüllen:

7 Spring absichern

276

Nun können wir damit beginnen, die Tags für die Sicherheitsattribute im Code unterzu-bringen. Das einzige Sicherheitsattribut, das Sie kennen müssen, ist SecurityConfig. Es verknüpft eine Berechtigung mit einer Methode. Der folgende Codeausschnitt zeigt, wie man die Methode enrollStudentInCourse() so durch Tagging abändert, dass sie die Rollen ROLE_ADMIN oder ROLE_REGISTRAR verlangt:

/** * @@org.acegisecurity.SecurityConfig("ROLE_ADMIN") * @@org.acegisecurity.SecurityConfig("ROLE_REGISTRAR") */ public void enrollStudentInCourse(Course course, Student student) throws CourseException;

Die Deklaration dieser Sicherheitsattribute für enrollStudentInCourse() ist gleich-wertig mit der Deklaration von objectDefinitionSource. wie sie in Abschnitt 7.6.1 er-folgte.

7.7 Zusammenfassung

Sicherheit ist ein wesentlicher Aspekt zahlreicher Anwendungen. Spring Security ist ein Mechanismus zur Absicherung von Anwendungen und fußt auf Springs Philosophie der losen Kopplung, der Dependency Injection und der aspektorientierten Programmierung. Sie haben vielleicht bemerkt, dass in diesem Kapitel sehr wenig Java-Code aufgelistet wurde. Seien Sie nicht allzu enttäuscht: Das Fehlen von Java-Code veranschaulicht ledig-lich eine bedeutende Stärke von Spring Security – die lose Kopplung zwischen einer An-wendung und ihrer Sicherheit. Sicherheit ist ein Faktor, der die Kernfunktionalitäten einer Anwendung übersteigt. Mit Spring Security können Sie Ihre Anwendungen schützen, ohne Sicherheitscode direkt in den Anwendungscode schreiben zu müssen. Ein weiterer Punkt, den Sie vielleicht bemerkt haben, ist die Tatsache, dass viele der Kon-figurationsdateien, die zur Absicherung einer Anwendung mit Spring Security erforderlich sind, gar nichts über die Anwendung wissen, die sie absichern. Die einzige Spring Securi-ty-Komponente, die wirklich etwas über die geschützte Anwendung wissen muss, ist die Objektdefinitionsquelle, wo Sie eine abgesicherte Ressource mit den Berechtigungen ver-knüpfen, die für deren Zugriff erforderlich sind. Die lose Kopplung funktioniert zwischen Spring Security und den Anwendungen also in beiden Richtungen.