Saturday, January 8, 2011

Spring Security - MVC: Using an Embedded LDAP Server

In this tutorial we will setup a simple Spring MVC 3 application, secured by Spring Security. ur users will be authenticated against an LDAP provider. This tutorial is exactly similar with the Spring Security - MVC: Using an LDAP Authentication Provider tutorial. The main difference now is we will be using an embedded LDAP server for testing our application.

What is LDAP?
The Lightweight Directory Access Protocol (LDAP) is an application protocol for reading and editing directories over an IP network. A directory is an organized set of records. For example, the telephone directory is an alphabetical list of persons and organizations, with each record having an address and phone number. A directory information tree often follows political, geographic, or organizational boundaries. LDAP directories often use Domain Name System (DNS) names for the highest levels. Deeper inside the directory might appear entries for people, departments, teams, printers, and documents.

Source: http://en.wikipedia.org/wiki/LDAP

How do we embed an LDAP server using Spring Security?
18.3.1 Using an Embedded Test Server

The <ldap-server> element can also be used to create an embedded server, which can be very useful for testing and demonstrations. In this case you use it without the url attribute:

<ldap-server root="dc=springframework,dc=org"/>

Here we've specified that the root DIT of the directory should be “dc=springframework,dc=org”, which is the default. Used this way, the namespace parser will create an embedded Apache Directory server and scan the classpath for any LDIF files, which it will attempt to load into the server. You can customize this behaviour using the ldif attribute, which defines an LDIF resource to be loaded:

<ldap-server ldif="classpath:users.ldif" />

Source: Spring Security 3 Reference
To convert our previous application from using an actual LDAP server to an embedded server, all we need to do is modify the

Let's open the spring-security.xml file and modify the tag to the new configuration:

// Old configuration



// New configuration


Here's the updated spring-security.xml:

spring-security.xml
<?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:security="http://www.springframework.org/schema/security"
 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.0.xsd">
 
 <!-- This is where we configure Spring-Security  -->
 <security:http auto-config="true" use-expressions="true" access-denied-page="/krams/auth/denied" >
 
  <security:intercept-url pattern="/krams/auth/login" access="permitAll"/>
  <security:intercept-url pattern="/krams/main/admin" access="hasRole('ROLE_ADMIN')"/>
  <security:intercept-url pattern="/krams/main/common" access="hasRole('ROLE_USER')"/>
  
  <security:form-login
    login-page="/krams/auth/login" 
    authentication-failure-url="/krams/auth/login?error=true" 
    default-target-url="/krams/main/common"/>
   
  <security:logout 
    invalidate-session="true" 
    logout-success-url="/krams/auth/login" 
    logout-url="/krams/auth/logout"/>
 
 </security:http>
 
 <!-- 
   For authentication:
   user-search-filter: the attribute name that contains the user name 
      user-search-base: the base path where to find user information
      
      For authorization:
      group-search-filter: the attribute name that contains the full dn of a user
      group-search-base: the base path where to find role information
      group-role-attribute: the attribute name that contains the role type
      role-prefix: the prefix to be added when retrieving role values
      
      For server access:
      manager-dn: the full dn of the person that has access to an LDAP server
      manager-password: the password of the person that has access to an LDAP server
 -->
 <security:authentication-manager>
         <security:ldap-authentication-provider  
           user-search-filter="(uid={0})" 
           user-search-base="ou=users"
           group-search-filter="(uniqueMember={0})"
           group-search-base="ou=groups"
           group-role-attribute="cn"
           role-prefix="ROLE_">
         </security:ldap-authentication-provider>
 </security:authentication-manager>
 
 <!-- Use an embedded LDAP server. We need to declare the location of the LDIF file
   We also need to customize the root attribute default is "dc=springframework,dc=org"  -->
 <security:ldap-server ldif="classpath:mojo.ldif"  root="o=mojo"/>
 
</beans>
For the embedded server to work, we need to include LDIF file in the classpath:

mojo.ldif
version: 1

dn: o=mojo
objectClass: organization
objectClass: extensibleObject
objectClass: top
o: mojo

dn: ou=users,o=mojo
objectClass: extensibleObject
objectClass: organizationalUnit
objectClass: top
ou: users

dn: ou=groups,o=mojo
objectClass: extensibleObject
objectClass: organizationalUnit
objectClass: top
ou: groups

dn: cn=User,ou=groups,o=mojo
objectClass: groupOfUniqueNames
objectClass: top
cn: User
uniqueMember: cn=John Milton,ou=users,o=mojo
uniqueMember: cn=Robert Browning,ou=users,o=mojo
uniqueMember: cn=Hugo Williams,ou=users,o=mojo
uniqueMember: cn=John Keats,ou=users,o=mojo

dn: cn=Admin,ou=groups,o=mojo
objectClass: groupOfUniqueNames
objectClass: top
cn: Admin
uniqueMember: cn=Hugo Williams,ou=users,o=mojo
uniqueMember: cn=John Keats,ou=users,o=mojo

dn: cn=Robert Browning,ou=users,o=mojo
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: Robert Browning
sn: Browning
uid: rbrowning
userPassword:: cGFzcw==

dn: cn=John Keats,ou=users,o=mojo
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: John Keats
sn: Keats
uid: jkeats
userPassword:: cGFzcw==

dn: cn=Hugo Williams,ou=users,o=mojo
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: Hugo Williams
sn: Williams
uid: hwilliams
userPassword:: cGFzcw==

dn: cn=John Milton,ou=users,o=mojo
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: John Milton
sn: Milton
uid: jmilton
userPassword:: cGFzcw==
Then we have to modify our pom.xml to include the necessary dependencies for the embedded ApacheDS server. The depencies are big, so it might take a while to download.

pom.xml
    <dependency>
     <groupId>org.apache.directory.server</groupId>
     <artifactId>apacheds-all</artifactId>
     <version>1.5.5</version>
     <type>jar</type>
     <scope>compile</scope>
    </dependency>
    <dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-api</artifactId>
     <version>1.5.6</version>
     <type>jar</type>
     <scope>compile</scope>
    </dependency>
    <dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-log4j12</artifactId>
     <version>1.5.6</version>
     <type>jar</type>
     <scope>compile</scope>
    </dependency>

To access the login page, enter the following URL:
http://localhost:8081/spring-security-ldap-embedded/krams/auth/login
To logout, enter the following URL:
http://localhost:8081/spring-security-ldap-embedded/krams/auth/logout
To access the admin page, enter the following URL:
http://localhost:8081/spring-security-ldap-embedded/krams/main/admin
To access the common page, enter the following URL:
http://localhost:8081/spring-security-ldap-embedded/krams/main/common
Here are the usernames and passwords:
rbrowning - pass
jkeats - pass
hwilliams - pass
jmilton - pass

That's it. We've managed to setup a simple Spring MVC 3 application, that's secured by Spring Security. We also used an embedded LDAP server for authenticating our users.

The best way to learn further is to try the actual application.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-security-ldap/

You can download the project as a Maven build. Look for the spring-security-ldap-embedded.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

15 comments:

  1. Hello !
    Excuses me for my English.
    Your tutorial is very interesting , but i don't understand something about users password why do we use "pass" instead of "cGFzcw==" as mentionned in .ldif file ?
    Thank you very much.

    rsoubeiga

    ReplyDelete
  2. @rsoubeiga, I believe cGFzcw== is the encrypted form of the password "pass".

    ReplyDelete
  3. I have used used your ldif file and used the SecurityNamespace congiguration. and I get null pointer exception.





    ERROR 2011-05-11 16:06:16,355 [main] - Server startup failed
    java.lang.NullPointerException
    at org.apache.directory.server.core.schema.DefaultSchemaService.initialize(DefaultSchemaService.java:380)
    at org.apache.directory.server.core.DefaultDirectoryService.initialize(DefaultDirectoryService.java:1425)
    at org.apache.directory.server.core.DefaultDirectoryService.startup(DefaultDirectoryService.java:907)
    at org.springframework.security.ldap.server.ApacheDSContainer.start(ApacheDSContainer.java:160)
    at org.springframework.security.ldap.server.ApacheDSContainer.afterPropertiesSet(ApacheDSContainer.java:113)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1479)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1417)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:295)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:292)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:580)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:900)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:455)
    at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:294)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:215)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:47)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3934)
    at org.apache.catalina.core.StandardContext.start(StandardContext.java:4429)
    at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045)
    at org.apache.catalina.core.StandardHost.start(StandardHost.java:741)
    at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045)
    at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:443)
    at org.apache.catalina.core.StandardService.start(StandardService.java:516)
    at org.apache.catalina.core.StandardServer.start(StandardServer.java:710)
    at org.apache.catalina.startup.Catalina.start(Catalina.java:587)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:290)
    at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:415)


    could you please help.

    ReplyDelete
  4. The Tutorial might work until ApacheDS 1.5.5

    The NullpointerException occurs since ApacheDS 1.5.6.

    ReplyDelete
  5. You will find a tutorial for Version 1.5.7 at
    http://svn.apache.org/repos/asf/directory/documentation/samples/trunk/embedded-sample

    ReplyDelete
  6. Note that the UnboundID LDAP SDK supports creating an in-memory directory server. This may be more effective.

    ReplyDelete
  7. @ff1959, thanks for the info. That project looks really interesting.

    ReplyDelete
  8. Spring Security provides special support for LDAP authentication on Active directory which also handles partial list exception. Its completely declarative and most easy.

    ReplyDelete
  9. Hi Krams,

    I'm having trouble in authenticating the user using embedded LDAP. The error that I'm receiving is below:

    [LDAP: error code 32 - NO_SUCH_OBJECT: failed for SearchRequest baseDn : '2.5.4.10=safeway.com' filter : '(0.9.2342.19200300.100.1.1=rsapl00)' scope : whole subtree typesOnly : false Size Limit : no limit Time Limit : no limit Deref Aliases : deref Always attributes : : Attempt to search under non-existant entry: o=safeway.com]; nested exception is javax.naming.NameNotFoundException: [LDAP: error code 32 - NO_SUCH_OBJECT: failed for SearchRequest baseDn : '2.5.4.10=safeway.com' filter : '(0.9.2342.19200300.100.1.1=rsapl00)' scope : whole subtree typesOnly : false Size Limit : no limit Time Limit : no limit Deref Aliases : deref Always attributes : : Attempt to search under non-existant entry: o=safeway.com]; remaining name '/'

    Any idea on how to resolve this?

    Thanks in advance.

    ReplyDelete
  10. Hi ,
    org.springframework.ldap.CommunicationException: Request: 9 cancelled; nested exception is javax.naming.CommunicationException: Request: 9 cancelled; remaining name ''
    org.springframework.ldap.support.LdapUtils.convertLdapException(LdapUtils.java:100)
    org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:319)
    org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:259)

    Any idea on how to resolve this?

    Thanks in advance.

    ReplyDelete
  11. How to know user account is disabled or locked

    ReplyDelete
  12. Has anyone tried it on Weblogic server. Not able to make this sample work as is. On tomcat/Wildfly, it is perfect.

    ReplyDelete
  13. I have read your blog its very attractive and impressive. I like it your blog.

    Spring online training Spring online training Spring Hibernate online training Spring Hibernate online training Java online training

    spring training in chennai spring hibernate training in chennai

    ReplyDelete
  14. Happy to found this blog. Good Post!. It was so good to read and useful to improve my knowledge as updated one, keep blogging. Hibernate Training in Electronic City
    Java Training in Electronic City

    ReplyDelete