Spring Security 3 integration with Active Directory LDAP
Introduction
This article describes the process for integrating Spring Security 3 with Microsoft Active Directory (AD) LDAP. Spring Security is a powerful and highly customizable authentication and access-control framework that can be used to secure web applications and web service end-points. For the sake of simplicity there are two user roles that will be applied in this example: Customer Service Officer and System Administrator. The fictitious Web TwoPointOh application being secured is called “Orbiks” and if you follow this approach in a production environment take a deep breath and digest the contents of the OWASP Top Ten Web Application Security Risks to ensure you understand potential security risks and know how to minimise application vulnerabilities.
Spring Security 3.1
The new AD specific Authentication Provider in Spring Security 3.1 simplifies integration with Active Directory.
The following dependencies from Spring Security 3.1.0.RELEASE were referenced in this example:
spring-security-configspring-security-corespring-security-ldapspring-security-web
It is also necessary to include a dependency for the latest release of spring-ldap.
Getting started
Specify the security filter chain in the web application deployment descriptor and the URL pattern it applies to. In our application, we want all URLs to be processed by the security filter so the following is used in web.xml:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Ensure the security application context is loaded in the web.xml as follows:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationcontext-security.xml
classpath:applicationcontext.xml
</param-value>
</context-param>
Security Application Context
Spring Security allows fine-grained access to resources including method-level and action-level authorization but to keep things simple I will be securing application URLs. The web application has a log in page and a sign-up page so these pages will be excluded from the security intercept pattern to allow users to hit them without obviously needing to authenticate.
Spring Security Application Context
Create a file called applicationcontext-security.xml in the /WEB-INF directory as follows:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:security="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<!-- LDAP server details -->
<security:authentication-manager>
<security:authentication-provider ref="ldapActiveDirectoryAuthProvider" />
</security:authentication-manager>
<beans:bean id="grantedAuthoritiesMapper" class="net.comdynamics.orbiks.security.ActiveDirectoryGrantedAuthoritiesMapper"/>
<beans:bean id="ldapActiveDirectoryAuthProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
<beans:constructor-arg value="comdynamics.net" />
<beans:constructor-arg value="ldap://bigldap.comdynamics.net:389/" />
<beans:property name="authoritiesMapper" ref="grantedAuthoritiesMapper" />
<beans:property name="useAuthenticationRequestCredentials" value="true" />
<beans:property name="convertSubErrorCodesToExceptions" value="true" />
</beans:bean>
<security:http auto-config="true" pattern="/**">
<!-- Login pages -->
<security:form-login login-page="/login.html" default-target-url="/user/home.html"
login-processing-url="/j_spring_security_check" authentication-failure-url="/login-error.html" />
<security:logout logout-success-url="/login.html"/>
<!-- Security zones -->
<security:intercept-url pattern="/admin/**" access="ROLE_ADMIN" />
<security:intercept-url pattern="/case-management/**" access="ROLE_CUSTOMER_SERVICE_OFFICER, ROLE_ADMIN" />
</security:http>
</beans:beans>
The wildcard pattern match will result in all requests for resources in these paths being matched against the user principal that is attempting to access these resources.
A login page has been specified which redirects the user to the default-target-url on successful login when processed by the Spring Security filter. Login error redirects the user back to the login page requesting them to re-enter valid credentials.
Note: Applications that are secured to corporate Active Directory servers with large numbers of users and organizational units should specify the user-search-base and user-search-filter in the ldap-authentication-provider to refine the search path and reduce LDAP search times.
Granted Authorities
Granted Authorities represent the actions that a user can perform when successfully authenticated. A user may have one or more granted authorities and these authorities represent a role that maps to an LDAP group. The following enum contains GrantedAuthorities for the application:
package net.comdynamics.orbiks.security;
import org.springframework.security.core.GrantedAuthority;
/**
* Maps groups defined in LDAP to roles for a specific user.
*/
public enum OrbiksAuthority implements GrantedAuthority {
// These roles are specified in the security application context and are
// mapped to LDAP roles by the AuthoritiesMapper
ROLE_CUSTOMER_SERVICE_OFFICER, ROLE_ADMIN;
public String getAuthority() {
return name();
}
}
Granted Authorities Mapper
Since the group names in AD are different to the roles specified in the security application context, a custom class has been written to map these roles to LDAP groups. The following table shows the correlation of Granted Authorities to LDAP groups.
| Application Role | Granted Authority Name | LDAP Group Name |
|---|---|---|
| Customer Service Officer | ROLE_CUSTOMER_SERVICE_OFFICER | Orbiks-CustomerServiceOfficer |
| System Administrator | ROLE_ADMIN | Orbiks-SystemAdministrator |
The groups should be contained within the same LDAP Organizational Unit (OU) and users should be added as members to these groups depending on their role. Users will need to authenticate with their domain account name (sAMAccountName) and password.
The custom GrantedAuthoritiesMapper implementation is as follows:
package net.comdynamics.orbiks.security;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
/**
* Maps groups defined in LDAP to roles for a specific user.
*/
public class ActiveDirectoryGrantedAuthoritiesMapper implements GrantedAuthoritiesMapper {
// Constants for group defined in LDAP
private static final String ROLE_ADMIN = "Orbiks-SystemAdministrator";
private static final String ROLE_CUSTOMER_SERVICE_OFFICER = "Orbiks-CustomerServiceOfficer";
public ActiveDirectoryGrantedAuthoritiesMapper() {
}
public Collection<? extends GrantedAuthority> mapAuthorities(
final Collection<? extends GrantedAuthority> authorities) {
Set<OrbiksAuthority> roles = EnumSet.noneOf(OrbiksAuthority.class);
for (GrantedAuthority authority : authorities) {
if (ROLE_CUSTOMER_SERVICE_OFFICER.equals(authority.getAuthority())) {
roles.add(OrbiksAuthority.ROLE_OFFICER);
} else if (ROLE_ADMIN.equals(authority.getAuthority())) {
roles.add(OrbiksAuthority.ROLE_ADMIN);
}
}
return roles;
}
}
Conclusion
Spring Security 3.1 now provides simplified integration with Active Directory. Authentication and authorization is performed with the domain account of the user as there is no requirement to use an LDAP bind account.
Spring Integration RSS Feed Reader
Introduction
This article describes the process for implementing an RSS Feed Reader in your application using the Spring Integration framework in combination with Spring 3.
The Spring Integration framework supports Enterprise Integration Patterns which are a common vocabulary and body of knowledge around asynchronous messaging architectures used to build integration solutions. Spring Integration enables lightweight messaging within Spring-based applications and supports integration with external systems via declarative adapters.
Inbound Channel Adapter
RSS feeds are processed using an Inbound Channel Adapter which is bound to a URL. The channel is configured to poll against the URL on a periodic basis (set as milliseconds) and process incoming messages with a payload in the format of a com.sun.syndication.feed.synd.SyndEntry instance. A service activator is configured to bind the channel to a message handler which implements the org.springframework.integration.core.MessageHandler interface to process the incoming message.
Maven project
I have specified Spring Integration and Rome dependencies in the Maven POM file to support processing of RSS feeds. The Rome dependency contains classes and interfaces in the com.sun.syndication package.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>feedchannel</artifactId>
<groupId>net.comdynamics.feedchannel</groupId>
<version>0.1-SNAPSHOT</version>
</parent>
<artifactIdrfeedchannel-core</artifactId>
<packaging>jar</packaging>
<name>rssreader Core</name>
<description>feedchannel - Core Module</description>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-asm</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>rome</groupId>
<artifactId>rome</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>rome</groupId>
<artifactId>rome-fetcher</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-feed</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>net.comdynamics.feedchannel.RSSReader</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>net.comdynamics.feedchannel.RSSReader</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.dstovall</groupId>
<artifactId>onejar-maven-plugin</artifactId>
<version>1.4.4</version>
<executions>
<execution>
<configuration>
<!-- Optional -->
<onejarVersion>0.97</onejarVersion>
<!-- Optional, default is false -->
<attachToBuild>true</attachToBuild>
<!-- Optional, default is "onejar" -->
<classifier>onejar</classifier>
</configuration>
<goals>
<goal>one-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<pluginRepositories>
<pluginRepository>
<id>onejar-maven-plugin.googlecode.com</id>
<url>http://onejar-maven-plugin.googlecode.com/svn/mavenrepo</url>
</pluginRepository>
</pluginRepositories>
</project>
The application will be packaged using One-JAR to create a standalone Java application containing dependent libraries in a single executable JAR file.
Spring Application Context
The Spring application context specifies the Inbound Channel Adapter and a URL to subscribe against, the Service Activator, and a Message Handler for processing incoming RSS message payload.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-feed="http://www.springframework.org/schema/integration/feed"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/feed http://www.springframework.org/schema/integration/feed/spring-integration-feed-2.0.xsd">
<int:channel id="feedChannel" />
<int-feed:inbound-channel-adapter id="feedAdapter" channel="feedChannel" url="http://www.formula1.com/rss/news/latest.rss">
<int:poller id="poller" fixed-rate="10000" max-messages-per-poll="100" />
</int-feed:inbound-channel-adapter>
<int:service-activator input-channel="feedChannel" ref="messageHandler"/>
<bean id="messageHandler" class="net.comdynamics.feedchannel.RSSMessageHandler"/>
</beans>
RSS Message Handler
The RSS Message handler is invoked by the poller and the handleMessage is responsible for acting on the incoming message.
package net.comdynamics.feedchannel;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.Message;
import com.sun.syndication.feed.synd.SyndEntryImpl;
import org.springframework.integration.MessagingException;
import org.springframework.integration.core.MessageHandler;
/**
* Handler for processing incoming RSS message payloads.
*
* @author gregsmith
*/
public class RSSMessageHandler implements MessageHandler {
private static final Log log = LogFactory.getLog(RSSMessageHandler.class);
public RSSMessageHandler() {
log.debug("Created RSSMessageHandler");
}
/**
* Handles the message if possible. If the handler cannot deal with the
* message this will result in a <code>MessageRejectedException</code> e.g.
* in case of a Selective Consumer. When a consumer tries to handle a
* message, but fails to do so, a <code>MessageHandlingException</code> is
* thrown. In the last case it is recommended to treat the message as tainted
* and go into an error scenario.
* <p/>
* When the handling results in a failure of another message being sent
* (e.g. a "reply" message), that failure will trigger a
* <code>MessageDeliveryException</code>.
*
* @param message the message to be handled
* @throws org.springframework.integration.MessageRejectedException
* if the handler doesn't accept the message
* @throws org.springframework.integration.MessageHandlingException
* when something fails during the handling
* @throws org.springframework.integration.MessageDeliveryException
* when this handler failed to deliver the
* reply related to the handling of the message
*/
public void handleMessage(Message<?> message) throws MessagingException {
SyndEntryImpl syndFeed = (SyndEntryImpl)message.getPayload();
log.debug("Message: " + syndFeed.getTitle() + " created " + syndFeed.getPublishedDate());
}
}
RSS feed reader bootstrap
This application is invoked from the command line, hence the use of the Spring ClassPathXmlApplicationContext to wire bean dependencies.
package net.comdynamics.feedchannel;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.channel.DirectChannel;
/**
* Bootstrap class for RSSReader
*
* @author gregsmith
*/
public class RSSReader {
private static final Log log = LogFactory.getLog(RSSReader.class);
private RSSMessageHandler rssMessageHandler;
private DirectChannel feedChannel;
public RSSReader() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
new String[] { "applicationContext.xml" });
log.debug("ApplicationContext is: " + context);
feedChannel = (DirectChannel) context.getBean("feedChannel");
rssMessageHandler = (RSSMessageHandler) context.getBean("messageHandler");
feedChannel.setShouldTrack(true);
feedChannel.subscribe(rssMessageHandler);
}
public static void main(String args[]) {
log.debug("RSS reader starting...");
new RSSReader();
log.debug("RSS reader polling!");
}
}
Packaging and execution
The application is built using Maven with the following directive:
mvn package
The packaged JAR file will be created using a suffix of one-jar.jar and can then be launched from the /target directory of the Maven project using a syntax of
java -jar application-name.one-jar.jar
Upon startup, incoming RSS messages will be processed by the message handler. In this example which processes the Formula1.com RSS feed, the title and date of publication for the message is displayed.
Message: Qualifying analysis - can McLaren halt Vettel's winning streak? created Sat Apr 16 23:51:00 NZST 2011 Message: Chinese Grand Prix - selected team & driver quotes created Sun Apr 17 21:39:00 NZST 2011 Message: Race - Hamilton takes sensational Shanghai win created Sun Apr 17 21:41:00 NZST 2011 Message: Sebastian Vettel Q&A - an afternoon full of lessons created Mon Apr 18 01:03:00 NZST 2011 Message: FIA post-race press conference - China created Mon Apr 18 01:10:00 NZST 2011 Message: Montezemolo demands rapid improvement at Ferrari created Mon Apr 18 23:59:00 NZST 2011 Message: China analysis - McLaren strike back in Shanghai created Tue Apr 19 03:41:00 NZST 2011 Message: Shanghai debrief with Williams' Sam Michael created Tue Apr 19 22:29:00 NZST 2011 Message: Massa determined to maintain China momentum created Wed Apr 20 20:46:00 NZST 2011 Message: Lewis Hamilton Q&A: China win just the start of the fight created Thu Apr 21 01:08:00 NZST 2011 Message: Q&A with McLaren's Jenson Button created Thu Apr 21 02:29:00 NZST 2011 Message: Peter Sauber Q&A: We've reached our first target? created Fri Apr 22 00:53:00 NZST 2011 Message: Robert Kubica Q&A: Fans' response has been overwhelming created Fri Apr 22 02:17:00 NZST 2011 Message: Formula One Fantasy - Mercedes' Nico Rosberg created Mon Apr 25 01:14:00 NZST 2011 Message: Kubica discharged from Italian hospital created Mon Apr 25 08:01:00 NZST 2011
Conclusion
Through minimal coding it is possible to subscribe to an RSS feed using the Spring Integration framework. Since processing RSS feeds is a trivial usage example, the reader is encouraged to explore the framework in more detail if there is a need to support asynchronous, message-driven behavior within a Spring-based application.


