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.
Active Directory bulk load
Introduction
Part of the configuration for an application I recently developed required creating a set of test users in Active Directory. For the sake of expediency, rather than manually create each user I scripted the creation by parsing a file with the csvde command line tool.
CSV User File
Create a CSV file with the following row header columns:
DN,objectClass,distinguishedName,givenName,sn,sAMAccountName
In subsequent rows insert values similar to the following for your LDAP server and increment the integers accordingly. For example:
"CN=Test User 01,CN=Users,DC=lynx,DC=comdynamics,DC=net",user,"CN=Test User 01,CN=Users,DC=lynx,DC=comdynamics,DC=net",Test User 01,testuser01 "CN=Test User 02,CN=Users,DC=lynx,DC=comdynamics,DC=net",user,"CN=Test User 02,CN=Users,DC=lynx,DC=comdynamics,DC=net",Test User 02,testuser02 etc... etc...
The above attributes specify a user object in an organizational unit of the LDAP tree using a first name, a surname with a unique account name (login) for the user.
Process User File
Load the users into the Active Directory server from the CSV using the following command:
csvde -i -k -f testusers.csv -s lynx.comdynamics.net
Set password for newly created users
As the csvde command does not set the password on import it is necessary to run dsmod looping through the user set as a post-import operation. Create a script with contents similar to the following:
FOR /L %%I in (1,1,9) DO dsmod user "CN= Test User 0%%I,CN=Users,DC=lynx,DC=comdynamics,DC=net" -pwd Password1 -pwdneverexpires yes FOR /L %%I in (10,1,20) DO dsmod user "CN= Test User %%I,CN=Users,DC=lynx,DC=comdynamics,DC=net" -pwd Password1 -pwdneverexpires yes FOR /L %%I in (20,1,30) DO dsmod user "CN= Test User %%I,CN=Users,DC=lynx,DC=comdynamics,DC=net" -pwd Password1 -pwdneverexpires yes
The above script processes a total of 30 users in the CSV file in batches of 10 and sets each user with a specific password that never expires.


