Die Realisierung einer Schnittstelle zu einem Ldap Respository ist nach wie vor mit einem erheblichen Implementierungsaufwand verbunden und gestaltet sich oft als sehr unflexibel z.B. gegenüber Änderungen am Ldap Schema.
Beispiel für ein Ldap Schema und einen Ldap Eintrag:
Im Folgenden wird eine Lösung vorgestellt, wie sich mit Hilfe eines Jdbc-Ldap Treibers und einem Persistenzframework relativ schnell eine flexible Ldap Schnittstelle realisieren lässt.
Kern der hier vorgestellten Lösung ist der Jdbc-Ldap Treiber. Dieser Treiber simuliert der Anwendung einen Jdbc konformen Treiber und erlaubt SQL ähnliche Anfragen bei der Abfrage des Ldap Servers. Basierend auf dem Treiber können anschließend Persistenzframeworks eingesetzt werden, welche den Treiber für die Abfragen nutzen und die zurückgegebenen Daten in Java Objekte übersetzen.
Verwendete Bibliotheken
Jdbc-Ldap Treiber 2.1 Download
iBatis Persistenzframework 2.3.4 Download
Springframework 2.5.6
Quellcode
Für das Auslesen der Nutzer aus dem Ldap Repository benötigen wir zunächst einmal eine Klasse die unseren Nutzer repräsentiert.
Als nächstes implementieren wir für das Auslesen der Nutzer ein Data Access Objekt (DAO).
Das Interface UserDao definiert nur eine Methode listAllUser. Diese gibt alle User als Liste zurück.
Die Klasse UserDaoImpl nutzt für den Zugriff auf die Nutzerdaten das iBatis Framework. Das Springframework bietet dafür Hilfsklassen an, wovon eine hier als Oberklasse verwendet wird (SqlMapClientDaoSupport).
Für die Konfiguration des iBatis Frameworks benötigen wir zwei Konfigurationsdateien (SqlMapConfig.xml und SqlMap.xml) in denen die SQL Statements und Objektmappings hinterlegt werden.
Die Datei SqlMap.xml enthält die Definition des LDAP-SQL Statements mit dem die Nutzerdaten aus dem Ldap Repository ausgelesen werden und legt unsere User Klasse als Ergebnistyp fest.
Am Ende benötigen wir noch die Spring- und Datenbank Konfiguration um unsere Demo Applikation ausführen zu können.
In der Datei database.properties werden für den Jdbc-Ldap Treiber die notwendigen Parameter hinterlegt.
Führt man nun die Anwendung aus und ruft die Methode listAllUser des UserDao aus wird eine Liste aller Nutzer des Ldap Repositories zurückgegeben.
Vorteile der Lösung:
Nachteile:
Neben den aufgeführten Vorteilen der Lösung darf ein gravierender Nachteil der hier aufgezeigten Lösung aber nicht unerwähnt bleiben. Nach genauerem Test des hier verwendeten Jdbc-Ldap Treibers erwies er sich leider nicht als fehlerfrei. Base64 kodierte Ldap Attribute werden von dem hier verwendetem Treiber nicht korrekt dekodiert so dass er sich leider nicht für einen produktiven Einsatz verwenden lies.
Kompletter Quellcode der Demoanwendung: Demo Quellcode
Auch bei der Pluginentwicklung für Confluence kommt irgendwann der Zeitpunkt, an dem man sich mit komplexeren Abläufen bei der Persistierung von Daten beschäftigen muss. Um die Integrität dieser Daten sicherstellen zu können, ist man dann auf den Einsatz von Transactions angewiesen. Da Confluence das Spring Framework verwendet, bietet sich hierfür die Nutzung des TransactionTemplate an. Dieses ermöglicht es, wie in der Spring Dokumentation beschrieben, auf einfache Weise kritischen Code unter Verwendung eines Callbacks in eine Transaction zu verpacken. Dazu benötigt man jedoch noch einen PlatformTransactionManager. Dieser implementiert die Strategie für die Transaktionsbehandlung, die im Fall von Confluence auf Hibernate basiert. Auch hierzu können weitere Informationen und ein Codebeispiel auf den Seiten von Spring gefunden werden. Der PlatformTransactionManager ist als Bean im Application Context von Confluence vorhanden und kann über den ComponentContainer referenziert werden:
public PlatformTransactionManager getTransactionManager(){
return (PlatformTransactionManager) ComponentContainer.get("transactionManager");
}
Allerdings funktioniert dieses Codebeispiel nach einer Umstellung auf V2-Plugins nicht mehr. Der Grund: Da diese Plugins in OSGI-Bundles umgewandelt werden, welche eine andere Spring Version verwenden als die Kern-Applikation, kommt es bei der Referenzierung der transactionManager-Bean über den ComponentContainer zu einer ClassCastException.
Bei der Suche nach einer Lösung für dieses Problem bin ich über ein Ticket im Issue Tracker von Atlassian gestolpert, in dessen Kommentaren eine Alternative beschrieben wird: Abhilfe schafft die Verwendung der Shared Access Layer (SAL), einer einheitlichen Service-Schicht für alle Atlassian Anwendungen. Sie stellt neben verschieden anderen Services auch ein TransactionTemplate bereit, das für Confluence bereits den auf Hibernate basierenden PlatformTransactionManager gesetzt hat und auf folgende Weise referenziert werden kann.
Zunächst muss das Template als Komponente (Spring-Bean) im Plugin-Descriptor (atlassian-plugin.xml) importiert werden:
<component-import name="SAL Transaction Template" key="salTransactionTemplate">
<interface>com.atlassian.sal.api.transaction.TransactionTemplate</interface>
</component-import>
Dann kann man sich die Komponente per Spring Autowiring in fast alle Plugin Modultypen (z. B. Actions und Components ) injizieren lassen. Der Name der zu injizierenden Bean entspricht dem Key des component-import Elementes im Plugin-Descriptor (in diesem Beispiel also “salTransactionTemplate” ):
public void setSalTransactionTemplate(TransactionTemplate template){
this.transactionTemplate = template;
}
Nun kann das Template wie oben beschrieben verwendet werden.
Noch ein Hinweis: SAL wird erst ab Confluence 3.0 mit ausgeliefert. Für Confluence 2.10 muss man also eine andere Lösung finden.
<
OpenBenno ist eine unter GPL lizenzierte E-Mail Archivierungslösung. Damit ist es möglich, die gesetzlichen Anforderungen zur Archivierung elektronischer Kommunikation umzusetzen. Soll diese Software im Unternehmen eingesetzt werden, ist zumeist eine Anbindung an ein vorhandenes Nutzerrepository via LDAP notwendig. Dieser Artikel beschreibt die notwendige Konfiguration der Anbindung an CAS und das Active Directory. Die zusätzlich notwendige Library, deren Sourcen und eine Beispielkonfiguration können am Ende des Artikels heruntergeladen werden.
Zur Authentifikation und Autorisation benutzt OpenBenno das Spring Security (ehemals Acegi) Framework. Dieses ermöglicht die Einbindung der Anwendung in eine Vielzahl von Wirkumgebungen. Die Authentifikation sollte im konkreten Fall über die Single-Sign-On-Lösung CAS erfolgen. Die Auflösung der Nutzerberechtigungen wird über die LDAP-Schnittstellt des Active Directorys erfolgen.
Die initiale Einbindung des CAS-Workflows wird von einem EntryPoint ermöglicht, der eine Weiterleitung zur CAS-Login-Seite vornimmt:
<http entry-point-ref="casProcessingFilterEntryPoint"> <intercept-url pattern="/login.html*" filters="none"/> <intercept-url pattern="/images/**" filters="none"/> <!-- adjust this role for your needs. "User" is a group, in that (in this case) --> <!-- everybody is a member, who should have access to the mail archive. --> <intercept-url pattern="/**" access="ROLE_USER"/> <form-login login-page='/login.html'/> <logout invalidate-session="true" logout-success-url="/login.html" /> </http> <beans:bean id="casProcessingFilterEntryPoint" class="org.springframework.security.ui.cas.CasProcessingFilterEntryPoint"> <beans:property name="loginUrl" value="https://www.mycompany.de/cas/login"/> <beans:property name="serviceProperties" ref="serviceProperties"/> </beans:bean> <beans:bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties"> <beans:property name="service" value="http://www.mycompany.de/bennosearch/j_spring_cas_security_check"/> <beans:property name="sendRenew" value="false"/> </beans:bean>
Die Rollendefinition ROLE_USER muss dabei so angepasst werden, dass dort eine Gruppe referenziert wird, der alle Nutzer angehören, die Zugriff auf das Mailarchiv erhalten sollen. An der Bean casProcessingFilterEntryPoint muss dabei die Login-URL des CAS definiert werden. In den serviceProperties muss die URL definiert werden, auf die das CAS bei erfolgreichem Login weiterleitet. Wichtig ist dabei die Angabe der speziellen URL (/j_spring_cas_security_check), auf welche ein spezieller CAS-Filter angesetzt wird:
<authentication-manager alias="authenticationManagerAlias"/> <beans:bean id="casProcessingFilter" class="org.springframework.security.ui.cas.CasProcessingFilter"> <custom-filter after="CAS_PROCESSING_FILTER"/> <beans:property name="authenticationManager" ref="authenticationManagerAlias"/> <beans:property name="defaultTargetUrl" value="/"/> <beans:property name="filterProcessesUrl" value="/j_spring_cas_security_check"/> </beans:bean>
Die Validierung des CAS-Tickets wird später von einem custom-authentication-provider durchgeführt. Dieser benötigt die Basis-URL des CAS:
<beans:bean id="casAuthenticationProvider" class="org.springframework.security.providers.cas.CasAuthenticationProvider">
<custom-authentication-provider/>
<beans:property name="userDetailsService" ref="userDetailsService"/>
<beans:property name="serviceProperties" ref="serviceProperties"/>
<beans:property name="ticketValidator">
<beans:bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
<beans:constructor-arg index="0" value="https://www.mycompany.de/cas/"/>
</beans:bean>
</beans:property>
<beans:property name="key" value="ticket"/>
</beans:bean>
Bei erfolgreicher Authentifizierung werden über den referenzierten userDetailsService die Rollen des Nutzers nachgeladen, welche später entscheiden, ob das Mailarchiv überhaupt zugänglich ist, und welche E-Mail-Adressen durchsucht werden können. Dabei wird auf das Active Directory zugegriffen.
<beans:bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<beans:constructor-arg value="ldap://ldap.mycompany.de:3268/dc=mycompany,dc=de"/>
<beans:property name="userDn" value="cn=publicLogin,cn=Users"/>
<beans:property name="password" value="publicLoginPassword"/>
</beans:bean>
<beans:bean id="userDetailsService" class="org.springframework.security.userdetails.ldap.LdapUserDetailsService">
<beans:constructor-arg index="0" ref="userSearch"/>
<beans:constructor-arg index="1" ref="authoritiesPopulator"/>
</beans:bean>
<beans:bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<beans:constructor-arg index="0" value="CN=Users"/>
<beans:constructor-arg index="1" value="(&(sAMAccountName={0})(objectClass=user))"/>
<beans:constructor-arg index="2" ref="contextSource"/>
</beans:bean>
Die Bean contextSource definiert die URL des LDAPs und den zu verwendenden Basispfad. Weiterhin müssen ein öffentlicher Nutzer und Passwort für einen erfolgreichen Bind angegeben werden. Die Bean userSearch definiert einen Filter zum Auffinden des eingeloggten Nutzers, wobei {0} ein Platzhalter für das eingegebene Login ist. Für den referenzierten authoritiesPopulator muss jedoch eine spezielle Komponente für OpenBenno verwendet werden:
<beans:bean id="authoritiesPopulator" class="de.communardo.openbenno.OpenBennoLdapAuthoritiesPopulator">
<beans:constructor-arg ref="contextSource"/>
<beans:constructor-arg value=""/>
<beans:property name="groupRoleAttribute" value="cn"/>
<beans:property name="groupSearchFilter" value="(&(member={0})(objectClass=group))"/>
<beans:property name="rolePrefix" value="ROLE_"/>
<beans:property name="searchSubtree" value="true"/>
<beans:property name="convertToUpperCase" value="false"/>
<beans:property name="mailRoleAttribute" value="mail"/>
</beans:bean>
Dieser authoritiesPopulator ist eine Ableitung vom standardmäßigen DefaultLdapAuthoritiesPopulator, der damit um eine Funktion erweitert wurde. Es wird nicht nur die Menge an zugewiesenen Nutzergruppen (aus dem Active Directory) in Rollenbezeichnungen für Spring Security überführt, sondern zusätzlich die E-Mail-Adresse des Nutzers als spezielle Rolle mit dem Präfix ROLE_MAIL_ bereitgestellt.
Im konkreten Fall bezeichnet das Attribut mail des authentifizierten Nutzers die jeweilige E-Mail-Adresse. Über die Property mailRoleAttribute kann dieser Wert angepasst werden. Optional kann über die Property roleMailPrefix der Präfix (standardmäßig ROLE_MAIL_) angepasst werden. Für die aktuelle Version von OpenBenno ist dies jedoch nicht notwendig.
Der OpenBennoLdapAuthoritiesPopulator überschreibt die getAdditionalRoles-Methode und entnimmt lediglich das Mail-Attribut aus dem LDAP-Nutzer und stellt diese, zusammen mit dem Präfix, als neue Authority bereit:
protected Set getAdditionalRoles(DirContextOperations user, String username) {
String mailAddress = user.getStringAttribute(mailRoleAttribute);
String role = roleMailPrefix + mailAddress.toLowerCase();
Set result = new HashSet();
result.add(new GrantedAuthorityImpl(role));
return result;
}
Die gesamte Klasse kann im Quellcode und als gepaktes JAR heruntergeladen werden.
Bereits im ersten Codeabschnitt wurde das Logout-Verhalten definiert:
<logout invalidate-session="true" logout-success-url="/login.html" />
Damit wird erreicht, dass nach einem Klick auf den Logout-Button zur Login-Seite gesprungen und die Session invalidiert wird. Um sich auf der dann gezeigten Login-Seite erneut einloggen zu können, muss LDAP noch für den direkten Login konfiguriert werden. Dies ist durch die Registrierung eines custom-authentication-providers möglich:
<beans:bean id="ldapProvider" class="org.springframework.security.providers.ldap.LdapAuthenticationProvider"> <custom-authentication-provider/> <beans:constructor-arg ref="ldapBindAuthenticator" /> <beans:constructor-arg ref="authoritiesPopulator"/> </beans:bean> <beans:bean id="ldapBindAuthenticator" class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator"> <beans:constructor-arg ref="contextSource"/> <beans:property name="userSearch" ref="userSearch" /> </beans:bean>
Mithilfe der beschriebenen Konfigurationsschritte ist die Anbindung von OpenBenno an CAS und LDAP (z.B. Active Directory) möglich. Um Ihr OpenBenno entsprechend zu konfigurieren, folgen Sie bitte diesen Schritten:
Unter http://springosgi.googlepages.com/ befindet sich ein hervorragendes Tutorial zu Spring-DM, welches nebenbei auch die Verwendung der Plugin-Development-Unterstützung von Eclipse erklärt. Wer einmal deren Vorzüge genossen hat, wird wahrscheinlich nie mehr Manifest-Dateien per Texteditor bearbeiten wollen. Außerdem ist es bei der Entwicklung angenehmer, entwickelte Bundles über einen einzigen Knopfdruck im Eclipse zu starten, anstatt sie z.B. über Maven zu bauen und den Container extern zu starten.
Trotzdem kann es wichtig sein, dass die Bundles unabhängig von Eclipse in Maven gebaut werden können, z.B. um komplette Releases mit Tokenersetzung für verschiedene Umgebungen zu bauen. Für Maven2 existiert das Felix Bundle Plugin (http://felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.html), welches fertige Bundles generieren kann.
Da sich die Verszeichnisstrukturen des Eclipse-Plugin-Development-Tools und eines Standard-Maven2-Projektes sehr unterscheiden, wird hier ein Weg beschrieben, wie beide Welten vereinigt werden können.
Als Verzeichnisstruktur des Maven-Projektes wird folgendes vorausgesetzt:
- src
- main
- bundle
- META-INF
- Manifest.MF
- java
Innerhalb von Eclipse muss aber das META-INF-Verzeichnis im Wurzelverzeichnis des Projektes liegen, damit es als Bundle gebaut wird. Deshalb legen wir das Plugin-Projekt innerhalb des Bundle-Ordners an und importieren alle benötigten Resourcen (in diesem Fall nur den java-Ordner) über Verlinkung in das Projekt. Dafür erstellt man einen neuen Ordner und selektiert unter Advanced die Checkbox Link to Folder in file system. Dann wählt man den Java-Ordner und fügt ihn zum Projekt hinzu. Über das Kontext-Menü -> Build Path -> Use as source folder kann dieser Ordner als Quellordner markiert werden.
Die Verzeichnisstruktur im Eclipse-Projekt sieht jetzt folgendermaßen aus:
- META-INF
- Manifest.MF
- java (Verlinkt)
Jetzt muss nur noch das Maven-Plugin so eingerichtet werden, daß es die Bundle-Konfiguration aus der vom Eclipse erzeugten Manifest-Datei übernimmt. Das kann z.B. so aussehen:
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>bundle</id>
<phase>package</phase>
<goals>
<goal>bundle</goal>
<goal>install</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<_failok>true</_failok>
<_include>src/main/bundle/META-INF/MANIFEST.MF</_include>
<Include-Resource>{maven-resources},META-INF/spring=src/main/bundle/META-INF/spring</Include-Resource>
</instructions>
</configuration>
</plugin>
Das _include -Tag bewirkt, dass für die Bundle-Generierung die von Eclipse erzeugte Manifest-Datei verwendet wird. Leider wird in Dieser der Klassenpfad Einträge enthalten, die im erzeugten Bundle nicht mehr vorhanden sind, z.B. der bin-Ordner, in welchem die kompilierten Klassen liegen. Das Bundle-Plugin bricht an dieser Stelle ab, obwohl ungültige Klassenpfad-Einträge in Equinox nicht zu Fehlern führen. Der einfachste Weg, dieses Problem zu umgehen, ist _failok auf true zu setzen. Eleganter wäre es, den Klassenpfad-Eintrag vorher zu manipulieren. Wichtig ist nur, dass der Klassenpfad-Eintrag ./ existiert, weil das Bundle-Plugin alle Klassen im Wurzel-Verzeichnis des Bundles ablegt.
Allerdings kann nicht davon ausgegangen werden, dass sich das Bundle auf Anhieb in einem extern gestarteten equinox genauso verhält, wie in Eclipse, da Eclipse zahlreiche Umgebungsvariablen setzt, die von den Default-Werten abweichen. Sollten also ClassNotFoundExceptions bei Klassen auftreten, welche normalerweise zur JRE gehören (z.B. org.w3c.dom.Node) und innerhalb von Eclipse nicht als Dependency eingebunden werden müssen, dann lohnt sich ein Blick in die System-Properties.
Properties prop = System.getProperties();
for (Object k : prop.keySet()) {
System.out.println(k + ” – ” + prop.getProperty(k.toString()));
}
Gibt alle Properties aus. Eine Beschreibung der einzelnen Parameter befindet sich hier
Wichtig sind vor allem die Parameter osgi.compatibility.bootdelegation und org.osgi.framework.system.packages.
Grails verwendet intern Spring MVC als Web Framework, wodurch man auch in den Genuss von Spring Webflow kommt. Die Konversationen lassen sich Grails-üblich mit wenig Aufwand implementieren. Wenn man jedoch innerhalb einzelner States alle States des Webflows in einem View ausgeben möchte, z.B. um den Fortschritt innerhalb des Flows zu zeigen, findet man keine Hilfe in der Grails Dokumentation. Nach dem ich herausbekommen hatte, wie man den FlowExecutionContext bekommt und mich ein wenig mit der Spring API außeinander gesetzt hatte, bin ich zu der folgenden Lösung gekommen:

Um daraus eine richtige Navigation zu machen, müsste man dem jeweiligen Link zu jedem State noch einen Parameter hinzufügen, dessen Wert man in dem Startzustand des Workflow Controllers auswertet und auf den Folgezustand mappt:

Vor einer Woche war es soweit. Die Tore der diesmal von Frank Anke (Saxonia Systems) gesponsorten 6. Java User Group in Sachsen öffneten sich und Eberhard Wolff von SpringSource konnte der bisher größten Teilnehmerschar der JUG Saxony (www.jugsaxony.org) das neue Produkt SpringSource dm Server vorstellen.
dm Server
In der Veranstaltung betonte Eberhard Wolff die Vorteile des dm Servers, die vor allem durch den Einsatz von OSGI in der Möglichkeit liegen, Applikationsmodule zur Laufzeit des Servers zu starten, zu stoppen und auszuwechseln. Durch den modularen Aufbau von Applikationen im dm Server ist es möglich, Teile der Applikation auszutauschen, nicht wie bisher üblich die gesamte Webapplikation selbst. Der dm Server selbst enthält dabei für den Einsatz in Enterprise Applikationen bereits Lösungen für Probleme, die im Einsatz von OSGI autreten können. Ein Beispiel dafür ist die Nutzung von Log4J in mehreren Modulen oder der Einsatz von Hibernate als Datasource.
Durch die Integration des Tomcat WebServers dürfte auch die Migration bestehender Applikationen ein Kinderspiel werden. Stufenweise ist es möglich, im ersten Schritt die in der Webapplikation vorhandenen Bibliotheken als OSGI Bundles zu nutzen und aus dem Deployment der WebApplikation zu entfernen. Durch diesen Schritt hat man bereits die Möglichkeit, Bibliotheken auszutauschen, ohne den Server neu starten zu müssen. SpringSource bietet zu diesem Zweck bereits ein Bundle Repository an, das OSGI-fähige OpenSource Bibliotheken aus dem Umfeld von Java und Spring enthält. Im zweiten Schritt lohnt es sich für solche Webapplikationen durch ein Refactoring geeignete Module herauszulösen und als OSGI Bundles zu deployen. Die Modularität der Applikationen und damit deren Wartbarkeit dürfte sich so wesentlich verbessern.
Ausblick
Neben der funktionalen Vorstellung des Servers, der die Brücke zwischen der Technologie OSGI und dem WebFramework Spring schließt, überzeugten auch die Themen, die in die Entwicklung des dmServers 2.0 eingeflossen sind. Hier sind vor allem die Integration des Applikationsgedanken über Plan Files in den OSGI Container oder das Clonen von Bibliotheken zu nennen, mit dem der gleichzeitige Betrieb von Bibliotheken mit unterschiedlichen Abhängigkeiten möglich wird. Eberhard Wolff regte auch am Beispiel des Werkzeuges Bundlor, dass für das Management der Abhängigkeiten und die Erzeugung von OSGI Manifesten genutzt werden kann, den Austausch und Feedback über den Blog von SpringSource an.
Fazit
Zusammenfassend kann man sagen, dass das Konzept des dm Servers überzeugend vorgestellt wurde. Alle, die bisher den Tomcat Webserver einsetzen, können mit geringen Migrationsaufwänden auf den dm Server wechseln und schrittweise die Vorteile ausnutzen. Auch in neuen Projekten sollte der Einsatz des dm Servers erwogen werden, da die Möglichkeiten zur Modularisierung von Applikationen erheblich verbessert werden.
In diesem Sinne: Viel Spass beim Ausprobieren. Jeder ist natürich herzlich willkommen, seine Erfahrungen hier als Kommentar zu posten.
Übrigens: Der Spring Source dm Server steht ab sofort in der Version 2.0 M1 zum Download zur Verfügung.
Erstellung eines CoreMedia-AuthenticationProviders für das Spring Security Framework
Neben dem Aufbau von geschlossenen Benutzergruppen (GBGs) im CMS kann es auch andere Gründe geben, das CoreMedia UserRepository für die Authentifizierung von Nutzern zu verwenden. Das Spring Security Framework (ehemals ACEGI Security) bietet Funktionalitäten für die Authentifizierung und Autorisierung von Nutzern. Im Folgenden Klassendiagramm sind die Interfaces und Klassen für die Authentifizierung dargestellt.

Standardmäßig sind im Framework bereits verschiedene Implementierungen vorhanden (z.B. für Datenbanken, LDAP u.a.). Mit wenig Aufwand kann man eigene Implementierungen wie die gegen das CoreMedia UserRepository implementieren.
Der AuthenticationManager ist der zentrale Einstieg für die Authentifizierung. Er kann mehrere verschiedene AuthenticationProvider verwalten. Über das Authentication-Objekt kann die Authentifizierung nur durch gewünschte Provider erfolgen (AuthenticationProvider#supports(Class authentication)).
Der UserDetailsService lädt das User-Objekt aus dem jeweiligen Repository per Namen (#loadUserByUsername(String username)). Der Provider überprüft das Passwort mit der Methode additionalAuthenticationChecks(...).
public class CMUserDao implements UserDetailsService{
...
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
UserRepository userRepository = capConnection.getUserRepository();
org.acegisecurity.userdetails.User user = null;
try {
Repository repository = ContentRepositoryFactory.instance().getContentRepository();
User cmsUser = userRepository.getUserByName(username);
Collection < Group > groups = new ArrayList < Group >();
if (cmsUser != null) {
groups = cmsUser.getGroups();
} else {
throw new UsernameNotFoundException(username);
}
Collection < GrantedAuthority > authorities = new ArrayList < GrantedAuthority >();
if (groups != null) {
for (Group group : groups) {
authorities.add(new GrantedAuthorityImpl(group.getName()));
}
}
user = new org.acegisecurity.userdetails.User(username, "", true, true, true, true,
authorities.toArray(new GrantedAuthority[0]));
} catch (InvalidLoginException e) {
throw new BadCredentialsException(e.getMessage(), e);
} catch (LicensesExceededException e) {
new BadCredentialsException(e.getMessage(), e);
} catch (ConnectionNotOpenException e) {
new BadCredentialsException(e.getMessage(), e);
}
return user; } ... }
public class CMAuthenticationProvider extends
AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider {
...
protected void additionalAuthenticationChecks(UserDetails aUserDetails,
UsernamePasswordAuthenticationToken aAuthenticationToken)
throws AuthenticationException {
String username = aUserDetails.getUsername();
String password = aAuthenticationToken.getCredentials().toString();
boolean isValid = capConnection.isValidLogin(username, null, password);
if (!isValid) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"), aUserDetails);
}
}
...
}
Benutzt man den AuthenticationProcessingFilter aus dem Framework wird nach erfolgreicher Authentifizierung das Authentication-Objekt in den SecurityContext geladen, einer ThreadLocal-Variable. Benutzt man einen eigenen Controller, muss man dies von Hand machen.
auth = authenticationManager.authenticate(...);
SecurityContextHolder.getContext().setAuthentication(auth);
<bean id="authenticationProcessingFilter"
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
...
</bean>
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="cmsDaoAuthenticationProvider" />
</list>
</property>
</bean>
<bean id="cmsDao" class="de.mse.authentication.CMUserDao"/>
<bean id="cmsDaoAuthenticationProvider"
class="de.mse.authentication.CMAuthenticationProvider">
<property name="userDetailsService">
<ref bean="cmsDao"/>
</property>
</bean>
Links
Am 13./14.9.2008 fand erstmals die Berlin.jar an der FHTW Berlin statt. Die Java Konferenz wurde durch die Java User Group Berlin Brandenburg sehr gut organisiert. Verteilt über beide Konferenztage gab es in fünf parallelen Tracks zahlreiche interessante Vorträge, sowie HandsOn Sessions und Workshops für die mehr als 250 Besucher. Einige Redner sind bereits von anderen Konferenzen bekannt. So gab Eberhard Wolff gleich zu Beginn Einblick in das Lösungsangebot von SpringSource, der Firma hinter dem Spring Framework. Im Anschluss gab Torsten Fink einen Überblick über die JBoss/SOA-Plattform mit allerlei Verweisen auf den Einsatz in der Praxis. Nachdem am Grillstand für das leibliche Wohl gesorgt wurde, brachte Alexander Greif anhand einer im Rahmen seines Vortrages erstellten Anwendung den Zuhörern die Funktionsweise der Grails Plattform näher. Abgerundet wurde der Tag durch Oliver Böhms Vortrag zu aspektorientierter Softwareentwicklung. Dabei wurde der Frage „Gibt es ein Leben nach Java und OO?“ nachgegangen. Mein Fazit: Aspektorientierte Programmierung ist eine sinnvolle Ergänzung (!) zur objektorientierten Entwicklung, aber sicher kein grundsätzlich neuer Ansatz. Den Weg in die Praxis hat sie schon seit längerer Zeit gefunden, wie z.B. das Spring Framework beweist.
Leider waren damit der erste Konferenztag und unser Besuch der Berlin.jar schon vorbei. Am Tag zwei haben wir u.a. die Vorträge der ubigrate GmbH (Drahtwanderung: WIIr machen den NäXTen Schritt) und der buschmais GbR (Integrationsmuster am Beispiel von Apache Camel, Paradigmenhochzeit: Felix und ServiceMix in trauter Zweisamkeit vereint, Modellierung statischer Domänenmodelle mit Xtext) verpasst, aber vielleicht gibt es ja schon bald ein Wiedersehen bei der JUG Saxony
Dieses wird es auf alle Fälle im kommenden Jahr bei der zweiten Ausgabe der Berlin.jar geben.
Wenn man in Spring-Anwendungen zum Beispiel den Aufruf von Methoden loggen will, aber nicht in jeder gewünschten Methode eine Log-Anweisung einfügen möchte, kann man das Logging zentral als Aspekt definieren. Es können damit die Methodenaufrufe aller Klassen geloggt werden, die als Bean durch Spring verwaltet werden.
Zu Beginn sollte die Bean für den Interceptor (advice) definiert werden.
<bean id="methodLoggingInterceptor" class="MethodLoggingInterceptor"/>
Danach kann eine advisor verwendet werden, der auf den Interceptor (advice) verweist und die joinpoints (die gewüschten Methoden) über einen regulären Ausdruck engrenzt.
<bean id="wsMethodLogger" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice"> <ref local="methodLoggingInterceptor"/> </property> <property name="patterns"> <list> <value>de.communardo.*get.*</value> <value>de.communardo.*process.*</value> </list> </property> </bean>
Dann muss den betreffenden Beans der neue Interceptor hinzugefügt werden. Hierbei ist auf die Reihenfolge zu achten.
<bean id="profileWebservice">
...
<property name="interceptorNames">
<list>
<value>serviceTransactionInterceptor</value>
<value>hibernateInterceptor</value>
<value>wsMethodLogger</value>
</list>
</property>
</bean>
Die Interceptor-Klasse kann zum Beispiel die Interfaces MethodBeforeAdvice, AfterReturningAdvice und ThrowsAdvice implementieren. In den entsprechenden Methoden kann dann je nach Aufrufzeitpunkt eine geeignete Meldung ins Log geschrieben werden.
import java.lang.reflect.Method; import org.apache.log4j.Logger; import org.springframework.aop.AfterReturningAdvice; import org.springframework.aop.MethodBeforeAdvice; import org.springframework.aop.ThrowsAdvice; public class MethodLoggingInterceptor implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice { private Logger log = null; public MethodLoggingInterceptor() { } public void before(Method method, Object[] args, Object object) throws Throwable { log = Logger.getLogger(object.getClass()); log.debug("Beginning method: " + method.getName()); } public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { log = Logger.getLogger(target.getClass()); log.debug("Ending method: " + method.getName()); } public void afterThrowing(Method method, Object[] args, Object target, Throwable ex) { log = Logger.getLogger(target.getClass()); log.debug("Exception in method: " + method.getName() + " Exception is: " + ex.getMessage()); } }
Hinweis für AndroMDA-Nutzer: Für Klassen, die über den Stereotyp <<Service>> verfügen, existiert der tagged value @andromda.spring.service.interceptors. Darüber lassen sich den Services im Modell zusätzliche Interceptors hinzufügen, die in die Liste der interceptorNames aufgenommen werden. Die Spring-Konfiguration der beteiligten Beans kann anhand des applicationContext merge-point in den Application Context integriert werden.
Bei der Verwendung von Spring stellt sich im Buildmanagement die Frage wie mit Properties umgegangen wird. So hat jeder Test-, Live- und Entwicklungsserver seine eigene Datenbank oder andere unterschiedliche Einstellungen. Eine oft gewählte Variante ist die Ersetzung der Properties durch den Buildprozess. Dies hat allerdings den Nachteil, dass für eine Änderung der Properties der Buildprozess neu ausgeführt werden muss.
Mit Spring bietet sich dabei eine Alternative: Hier kann ein Propertyfile welches z. B. im Classpath liegt eingebunden werden:
<bean id="propertyPlaceholder" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:hibernate.properties" />
</bean>
Die Properties, die dann in dem File ‘hibernate.properties’ definiert sind, können innerhalb der Springkonfiguration genutzt werden:
...
<property name="hibernateProperties">
<props>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
...
</props>
</property>
...
Das Property File selbst ist dabei einfach wie erwartet:
hibernate.show_sql=true
hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
hibernate.hbm2ddl.auto=update
Releaseparty at Atlassian? Confluence 3.2 BETA and 3.1.2 with soms bugfixes were released yesterday. [...]
Tino Schmidt's Vortrag zu Enterprise Mashups auf der webciety, 4.3 Remix the Web http://bit.ly/d26rtA [...]
neuer Blogpost: February Cumulative Update (2010) http://bit.ly/cwxZGE [...]
Webinar am 16.03.: „Communote Enterprise Microblogging - Funktionen und Einsatzbereiche im Unternehmen“ http://bit.ly/96eexF [...]