Spring Security Active Directory LDAP Example
At a recent client, I was tasked with securing their web applications using Spring Security and their internal Active Directory (AD) LDAP server.
I scoured the web and went through a lot of trial and error until I finally found the configuration that worked in their environment. Based on some of the comments and questions I found on the web, the problems that I was facing seemed to be shared by others. Here’s a Spring Security Active Directory example to show how I was finally able to get Spring Security to work with the Active Directory LDAP server.
Note: This article does not go into the details of using Spring Security. You can find the Spring Security documentation at: Spring Security 4.0.3 Documentation.
Versions
The examples below are using:
- Spring Security: 4.0.3.RELEASE
- Spring (web, core, etc): 4.2.3.RELEASE
Configuration
Dependencies
Add the Spring Security dependencies to your application’s pom.xml file:
<!-- Spring Security --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.0.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.0.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-ldap</artifactId> <version>4.0.3.RELEASE</version> </dependency>
Deployment Descriptor
Add the Spring Security filter to your application’s web.xml file:
<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>
Also, in the web.xml, ensure that you are loading the context using the ContextLoaderListener and the context is not loaded again by the DispatcherServlet :
<context-param> <param-name>contextConfigLocation</param-name> <!-- replace with your spring context file --> <param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>mvc-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <!-- set to blank to ensure context is only loaded once --> <param-name>contextConfigLocation</param-name> <param-value></param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>mvc-dispatcher</servlet-name> <!-- url base for your spring app, not security config. That is handled by the filter --> <url-pattern>/*</url-pattern> </servlet-mapping>
Note: I had an issue where the Spring context would try to load and then unload with a failure after I added the <filter> to the web.xml. To fix this, I had to move the Spring context file to the <context-param> and use the ContextLoaderListener as shown above.
Spring Context
Configure your Spring context:
<beans xmlns="http://www.springframework.org/schema/beans" ... xmlns:sec="http://www.springframework.org/schema/security" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd ... http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> ... <!-- Begin Web Security --> <!-- unsecured resource if needed --> <sec:http pattern="/images/**" security="none"/> <sec:http> <!-- configure the roles allowed to access the app --> <sec:intercept-url pattern="/**" access="hasAnyRole('MANAGER', 'USER')"/> <!-- add more urls/patters/roles to refine security --> <sec:form-login/> <sec:logout/> <!-- if you are adding to an exiting app, you may need to disable CSRF protection until you can make application changes. --> <!-- sec:csrf disabled="true"/ --> </sec:http> <!-- add the properties below to your app's properties file or replace with hardcoded values to get working --> <sec:ldap-server id="contextSource" url="ldap://${ldap.server}:${ldap.port}/" manager-dn="${ldap.manager.user}" manager-password="${ldap.manager.password}"/> <sec:authentication-manager erase-credentials="true"> <sec:authentication-provider ref='ldapAuthProvider' /> </sec:authentication-manager> <!-- using bean-based configuration here to set the DefaultLdapAuthoritiesPopulater with ignorePartialResultsException=true. This is a known Spring/AD issue and a workaround for it --> <bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider"> <constructor-arg> <!-- the bind authenticator will first lookup the user using the service account credentials, then attempt to bind the user with their password once found --> <bean id="bindAuthenticator" class="org.springframework.security.ldap.authentication.BindAuthenticator"> <constructor-arg ref="contextSource" /> <property name="userSearch" ref="userSearch" /> </bean> </constructor-arg> <constructor-arg> <bean class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator"> <constructor-arg ref="contextSource" /> <constructor-arg value="DC=company,DC=com" /> <!-- group search base --> <!-- <property name="defaultRole" value="ROLE_USER" /> You can add a default role to everyone if needed --> <property name="searchSubtree" value="true" /> <property name="ignorePartialResultException" value="true" /> <property name="groupSearchFilter" value="(member={0})" /> </bean> </constructor-arg> </bean> <bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch"> <constructor-arg index="0" value="DC=company,DC=com" /> <constructor-arg index="1" value="(sAMAccountName={0})" /> <constructor-arg index="2" ref="contextSource" /> <property name="searchSubtree" value="true" /> </bean> <!-- end Web Security --> </beans>
Here are the properties used above:
# Ldap server access ldap.server=LDAP01 ldap.port=389 ldap.manager.user=cn=LdapAuthUser,ou=ServiceAccounts,dc=company,dc=com ldap.manager.password=SomePassword!
How it works
When the LdapAuthenticationProvider is performing the authentication, it will:
- Bind to LDAP using the manager user id and password specified in the <sec:ldap-server>
- Perform a lookup on the user id (entered from the login screen) using the userSearch bean
- Get the fully distinguished name of the user that matches
- Use that user and the password (entered) to bind to LDAP again
- Search for all of the groups the user is in based on the groupSearchFilter configuration
If you set your root logger to “debug”, log entries detailing this process will be output (including search result details). This was a huge help to me as I worked through the configuration.
To verify that there aren’t any LDAP configuration issues, or wrong credentials, you can use an LDAP browser (links below) to manually emulate this process.
Other Notes
- For help with LDAP, it is nice to have an LDAP browser installed such as http://jxplorer.org/ or http://www.ldapadmin.org/
- The configuration above will automatically make the /logout url available. You should add a link to it in your application.
- To get the username from inside a controller or service, use: SecurityContextHolder.getContext().getAuthentication().getName()
Hi Neil,
Thank you very much for this article!
It saved me a lot of time debugging.
hi Neil,
I am very new to spring. can i get the source code for the above.?
hi neil 🙂
do you have this example with annotations instead of xml config?
Hello Neil,
In my case I have to use a prefix in username PREFIXUSERNAME how can I do this in my XML?
Thank you in advance