Wednesday, April 10, 2024

2 Ways to setup LDAP Active Directory Authentication in Java - Spring Security Example Tutorial

The LDAP authentication is one of the most popular authentication mechanism around the world for enterprise application and Active directory (an LDAP implementation by Microsoft for Windows) is another widely used LDAP server. In many projects, we need to authenticate against active directory using LDAP by credentials provided in the login screen. Sometimes this simple task gets tricky because of various issues faced during implementation and integration and no standard way of doing LDAP authentication in a Java web application. Even though Java provides LDAP support but in this article, I will mostly talk about spring security because of it's my preferred Java framework for authentication, authorization, and security-related stuff.

We can do the same thing in Java by writing your own program for doing LDAP search and then LDAP bind but as I said its much easier and cleaner when you use spring security for LDAP authentication.

Along with LDAP Support, Spring Security also provides several other features which are required by enterprise Java application like Role-based Access Control, SSL Security, encryption of passwords and session timeout facilities.

Btw,  a decent knowledge of the Spring Framework is required to effectively use Spring Security in your project. It's not mandatory but unless you understand core concepts like Spring bean, dependency injection, container and how Spring works, it would be very difficult to use Spring security properly.

And, if you are not familiar with Spring security, it's better to spend some time learning it and if you need a recommendation, there is no better course than Spring Framework 5: Beginner to Guru by John Thompson on Udemy, one of the most up-to-date courses which cover Spring 5.0 features like Reactive development, etc.




1. LDAP Authentication Basics

Before getting deep into LDAP authentication on Active Directory, let's get familiar with some LDAP term because most of the time user is doing it the first time and they are not very familiar with typical LDAP glossary such as Dn, Ou, Bind or search, etc.

Dn - Distinguished name, a unique name which is used to find the user in LDAP server like Microsoft Active Directory.

Ou - Organization Unit

Bind - LDAP Bind is an operation in which LDAP clients sends bind request to LDAP user including username and password and if LDAP server able to find user and password correct, it allows access to the LDAP server.

Search - LDAP search is an operation which is performed to retrieve Dn of the user by using some user credential.

Root - LDAP directory's top element, like Root of a tree.

BaseDn - a branch in LDAP tree which can be used as a base for LDAP search operation like dc=Microsoft,dc=org"

If you want to know more about LDAP and Spring Security integration, you can also check out Spring Security Core: Beginner to Guru by John Thompson on Udemy. It's a great course to learn essential Spring Security concepts. 

Spring Security LDAP Integration and SAML Extension on Udemy






2. LDAP Authentication in Active Directory Spring Security

There are two ways to implement active directory authentication using LDAP protocol in spring security, the first way is a programmatic and declarative way which requires some coding and some configuration.

On the other hand, the second way is an out of box solution from spring security which just requires configuring ActireDirectoryAuthenticationProvider and you are done. we will see both approaches but I suggest using the second one because of its simplicity and easy to use a feature.

2.1 Active Directory Authentication using LDAP in Spring Security -Example 1

Configuration
Add the following configuration into your spring application-context.xml file, I would suggest putting this configuration in a separate application-context-security.XML file along with other security-related stuff.


1) Configuring LDAP Server
In order to configure LDAP server, please put following XML snippet into Spring security configuration file:

<s:ldap-server 
  url="ldap://stockmarket.com"   //ldap url
  port="389"                    //ldap port
  manager-dn="serviceAcctount@sotckmarket.com" //manager username
  manager-password="AD83DgsSe"                 //manager password
/>      

This configuration is self-explanatory but briefly few lines about manager-in and password, LDAP authentication on the active directory or any other LDAP directory is performed in two steps first an LDAP search is performed to locate Dn(Distinguished Name) of the user and then this Dn is used to perform LDAP Bind.

If the bind is successful than user authentication is successful otherwise it fails. Some people prefer remote compare of password than LDAP bind, but LDAP bind is what you mostly end of doing. 

Most of the Active directory doesn't allow Anonymous Search operation, so to perform an LDAP search your service must have an LDAP account which is what we have provided herein manager-in and manager-password.property.

In Summary, now LDAP login will be done in these steps:
  1. Your Service or application binds itself with LDAP using manager-dn and manager-password.
  2.  LDAP search for the user to find UserDn
  3.  LDAP bind using UserDn
That's complete the LDAP login part.

Now, let's move to next part of configuration LDAP authentication provider. 



2) Configuring LDAP Authentication Provider

This section specifies various authentication provider in spring-security here you can see your LDAP authentication provider and we are using userPrincipalName to search user inside Microsoft's Active directory.

<s:authentication-manager erase-credentials="true">
<s:ldap-authentication-provider
   user-search-base="dc=stockmarketindia,dc=trader"
   user-search-filter="userPrincipalName={0}"
/>

<s:authentication-provider 
  ref="springOutOfBoxActiveDirecotryAuthenticationProvider"/>
</s:authentication-manager>


Now a small piece of coding is needed to pass the userPrincipalName and authenticate the user.

public boolean login(String username, String password) {
   AndFilter filter = new AndFilter();
   ldapTemplate.setIgnorePartialResultException(true); // Active 
   Directory doesn’t transparently handle referrals. This fixes that.
 
   filter.and(new EqualsFilter("userPrincipalName", username));
   return ldapTemplate.authenticate("dc=stockmarketindia,dc=trader", 
             filter.toString(), password);

}       

line 2 is very important in this program because I spent the whole day figuring out when my application was repeatedly throwing javax.naming.PartialResultException: Unprocessed Continuation Reference(s)

you can also use sAMAccountName for the searching user, both userPrincipalName and sAMAccountName are unique in the Active Directory

What is most important here is that it has to be full name e.g. name@domain like jimmy@stockmarket.com.

The authenticate() method will return true or false based on a result of the bind operation. Btw, if you want to learn more about LdapTempalte class then I suggest you check  Learn Spring Security Masterclass by Eugen Paraschiv, which is a comprehensive course and covers Spring Security 5 as well.


LDAP authentication active directory spring Security Example





2.2 Active Directory Authentication using LDAP in Spring Security - Simpler Example

The second approach is much simpler and cleaner because it comes out of the box, you just need to configure LDAP server URL and domain name and it will work like cream.

<s:authentication-manager erase-credentials="true">
   <s:authentication-provider ref="ldapActiveDirectoryAuthProvider"/>
</s:authentication-manager>

<bean id="ldapActiveDirectoryAuthProvider" 
class="org.springframework.security.ldap.authentication.ad.
                ActiveDirectoryLdapAuthenticationProvider">
  <constructor-arg value="stockmarket.com" />  //your domain
  <constructor-arg value="ldap://stockmarket.com/" />  //ldap url
</bean>


That's it, done. 

This configuration will both authenticate and load all the granted authorities from LDAP like a group which you are a member of. This is integrated with spring security login element also.

If you are not familiar with GrantetAuthority and Access Control List in Spring Security then I suggest you go through the Learn Spring Security Certification Class course by Eugen Paraschiv, which covers this topic in good detail for both XML and Java Configuration.


2.3 Dependency

This example is based on spring security 3.0 and I was using spring-ldap-1.3.1.RELEASE-all.jar and spring-security-ldap-3.1.0.RC3.jar. 

If you don't know how to download Spring framework JAR files, follow the steps given in this Spring Framework JAR download Guide, which explains how to download Spring framework and other related JAR from Maven Central. 



2.4 Errors during LDAP authentication

you need to be very lucky to complete LDAP authentication against Active directory without any error or exception, here I am listing down some common error which I encountered and their solutions for quick reference.

1) javax.naming.PartialResultException: Unprocessed Continuation Reference(s); remaining name 'dc=company,dc=com'
This error comes because Microsoft Active Directory doesn't handle referrals properly and to fix this set this property

ldapTemplate.setIgnorePartialResultException(true);

2) javax.naming.NameNotFoundException: [LDAP: error code 32 - No Such Object]; remaining name ''
This error solved with some trial and error and mainly came due to an invalid format of username. it solved by providing full name e.g. jemmy@stockmarket.com



2.5 Tools

LDAP Browser: Having some tools to look data inside LDAP directory is best it gives you some visibility as well as means to browse data in LDAP. 

It's called an LDAP browser and there is a lot of open source LDAP browser available in web, like the jexplorer. you can browse and see data inside Active Directory by using LDAP browser.


2.6 LDAP Active directory Authentication over SSL

This works perfectly to implement LDAP authentication against Microsoft active directory. but one thing you might want to put attention is that with LDAP username and password travel to LDAP server as clear text and anyone who has access to LDAP traffic can sniff user credential so it's not safe. 

One solution is to use LDAP( LDAP over SSL) protocol which will encrypt the traffic travels between LDAP client and server.

This is easy to do in spring-security what you need to change is the URL instead of "ldap://stockmarket.com/" you need to use ""ldaps://stockmarket.com/". actually, a port for LDAP is 339 and for LDAPS is 636 but that's been taken care by spring in the second approach, in the first approach you need to provide this information.

What problem you may face is "unable to find valid certification path to requested target"

Exception  as shown below:
javax.net.ssl.SSLHandshakeException:
sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: 
    unable to find valid certification path to requested target

The reason of this Exception is simple, Certificate returns during SSL handshake are not signed by any trusted Certification Authority(CA) which is configured in you JRE Keystore e.g Verisign, Thawte, GoDaddy or entrust, etc. Instead, Server is sending a certificate which is not known to JRE.

To solve this problem you need to add certificates returned by Server into JRE's keystore. Btw, if you are confused between the key store and trust store then please read my article difference between keystore and trust store in Java to first learn about it. 

LDAP Active Directory Authentication in Java - Spring



2. 7 What I did to solve the problem

Nothing surprising, I use an open-source program called InstallCert.java, just run with your LDAP server and port and it will try to connect LDAP server using SSL and first throw same "PKIX path building failed" and then Certificates returned by LDAP server. 

It will then ask you to add Certificate into keystore just give certificate number as appeared on your screen and it will then add those certificate into "jssecacerts" inside C:\Program Files\Java\jdk1.6.0\jre\lib\security folder. Now re-run the program that error must be disappeared and

It will print:

"Loading KeyStore jssecacerts...

Opening connection to stockmarket.com:636...

Starting SSL handshake...

No errors, the certificate is already trusted


We are done, now if you try authenticating against LDAPS you will succeed.


There are many other approaches to perform LDAP authentication against active directory even without spring security by using Java. but I found spring-security very helpful so consider using it for your security requirement. let me know if you face any issue during LDAP login and I'll try my best to help you.

Further Learning
Spring Framework 5: Beginner to Guru
Spring Security Fundamentals by Bryan Hassen
P.S. - If you are an experienced Java/JEE Program and want to learn Spring Security end-to-end, I recommend the Learn Spring Security course by Eugen Paraschiv, The definitive guide to secure your Java application. It's useful for both junior and experienced Java Web developers.

32 comments:

  1. I was looking for some spring ldap authentication example and found this nice spring ldap tutorial. Thank you so much for explaining it so clearly.I have implemented spring ldap authentication successfully by following your step and most importantly I also hit by same exception and glad I had the solution in place. best spring ldap tutorial in my opinion

    ReplyDelete
  2. What I like in this spring ldap tutorial is clear example. spring ldap example is very clear and step by step in both way.I agree implementing ldap authentication in Java using JNDI is lot tougher than spring.

    ReplyDelete
  3. Prior to spring security there was no standard way of doing ldap authentication in Java. though ldap authentication using JNDI and Java was still possible it takes lot of time to get settings right and troubleshooting. This kind of code example for ldap authentication makes task lot easier. going forward spring ldap and Java is way to go.

    ReplyDelete
  4. How can we do ldap authentication without spring security but still using spring. I think there is an spring ldap api called spring-ldap which provides ldap authentication functionality , correct me if I am wrong ?

    ReplyDelete
  5. I was looking for some spring ldap integration code example and found your post. its amazing man I am thrilled with detailed description and error handling. thanks for your effort on this important topic of spring ldap integration.

    ReplyDelete
  6. Excellent Spring security ldap example. I was looking for an spring security ldap sample to get myself started in spring security and I come to you nice blog.Thanks for your spring security ldap tutorial and other java stuff. Good job keep it up.

    ReplyDelete
  7. spring-security-ldap-3.1.0.RC3.jar actually requires spring security 3.1, which in turn requires a few other 3.1 jars. otherwise, good tutorial, thanks!

    ReplyDelete
  8. Nice ldap authentication sample. keep it up. What I like most is SSL part and how to authenticate using SSL in Active directory. Indeed without SSL ldap authentication is completely insecure and anybody can gain access to user credentials including password.

    ReplyDelete
  9. Does anyone know how, using ActiveDirectoryLdapAuthenticationProvider, the authorities that the user has are made accessible? Does anything else need to be configured? Basically, I want to restrict url access by role, and I want to ensure that the roles are available and ready to use within my Spring application context configuration.

    ReplyDelete
  10. Hi,

    This is a nice tutorial and explains well how to use LDAP authentication.
    I had a problem which might be faced by any one in the real world. I wanted authentication from the LDAP Active directory and authorization from the local database system. The problem got even bigger while LDAP authentication is also doing authorization and my database authorization is getting overridden by that. What I have done is I have re-written ActiveDirectoryLdapAuthenticationProvider class and changed the implementation of method loadUserAuthorities as below -

    --------------------------------
    protected Collection loadUserAuthorities(DirContextOperations userData, String username, String password) {

    List authorities = new ArrayList();
    List userAuthorities = userService.loadUserAuthorities(username);

    if (CollectionUtils.isNotEmpty(userAuthorities))
    {
    for (String currentAuthority: userAuthorities)
    {
    authorities.add(new SimpleGrantedAuthority(currentAuthority));
    if (logger.isDebugEnabled()) {
    logger.debug("'memberOf' attribute values: " + currentAuthority);
    }
    }
    }
    else
    {
    logger.debug("No values for 'memberOf' attribute.");
    }
    return authorities;
    }
    -------------------------------------------

    Please let me know if any one can suggest other better approach.

    Regards,
    Maulik Kayastha

    ReplyDelete
  11. I’m setting a bind from spring ldap to ADAM repository, it’s works when you use a ADAM account and simple authentication but I need do the bind with a windows account (LSA).

    Could you please help me?

    Added the part of the code:

    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY,”com.sun.j ndi.ldap.LdapCtxFactory”);
    env.put(Context.PROVIDER_URL,param.getLdapUrl()); //replace with your server URL/IP
    env.put(Context.SECURITY_AUTHENTICATION,”DIGEST-MD5″);
    env.put(Context.SECURITY_PRINCIPAL,userName); // in format domain\username or username@domain
    env.put(Context.SECURITY_CREDENTIALS, passWord); //the password

    DirContext ctx = new InitialDirContext(env);

    This is raising the next error: error code 49 – 80090308 data 57

    ReplyDelete
  12. Hi, I am getting java.net.UnknownHostException and javax.naming.CommunicationException while deploying my spring security application on Websphere Application Server 7.0 (WAS). Application was running fine on Tomcat 6.0 and Tomcat 7.0 but giving following exception while deployment on WAS. My Question is is there any additional LDAP Setup required on WAS ? I don't think so because LDAP url and domain are already supplied to Spring via Configuration. Please help.

    ReplyDelete
  13. @Anonymous, apologies I could reply your comment earlier. glad to hear your problem resolved. would be great if you could post issue and how did you solved for all of us.
    Thanks
    Javin

    ReplyDelete
  14. @Javin
    Earlier i had problems with conf files. there were multiple context files (as in spring-security-tutorial which uses in-memory authentication). Later I removed one of them. The issue resolved.

    ReplyDelete
  15. One of the best Spring Security LDAP example, I have seen in web. I really like the Introduction and configuration part and seems easy to configure LDAP in Spring Security by reading your post.

    ReplyDelete
  16. Hi ,

    In the debug mode , i got this ,

    Got LDAP context on server ldap://testldap:389/dc=ab,dc=cd,dc=ef

    also
    searching forSearching for user 'xxxxx', with user search [ searchFilter: '(&(o
    ry=Person)(sAMAccountName={0}))', searchBase: 'DC=ab,DC=cd,DC=ef',scope: subtree, searchTimeLimit: 0, derefLinkFlag: false ]

    i am not able to authenticate stil...

    what could be the error ?Please help..

    ReplyDelete
  17. Just to add, LDAP protocol has two version LDAPv2 and LDAPv3, in LDAPv3 bind operation is optional and can be done any state of connection. Spring Security uses LDAPv3 Simple bind so it must be encrypted using SSL or TLS because Simple bind sends user password in clear text or plain text. bind operation also pass version of ldap protocol which is typically 2 or 3. While sending bind request you LDAP library should use LDAPv3 is the default version in protocol but not in library.

    ReplyDelete
  18. is it possible to use option #2 and get custom attributes from AD like name, CN, phone, email?

    ReplyDelete
  19. I'm getting a ActiveDirectoryLdapAuthenticationProvider handleBindException Active Directory authentication failed: Supplied password was invalid error
    Is there a way to specify userDN and password or a way to tie up a ContextSource with this setting?

    ReplyDelete
  20. I'm trying to follow the second approach- Active Directory Authentication using LDAP in Spring Security - 2
    How/Where do we specify the userDn/password for the LDAP Bind operation?

    ReplyDelete
  21. i tried the 2nd way but i got error.....
    ------------------------------------------------------
    ERROR [io.undertow.request] (default task-4) UT005023: Exception handling request to /eFin_iAnalytics/j_spring_security_check: java.lang.NoSuchMethodError: org.apache.commons.lang.StringUtils.replaceEach(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)Ljava/lang/String;
    at org.springframework.ldap.core.DistinguishedName.unmangleCompositeName(DistinguishedName.java:250) [spring-ldap-core-1.3.2.RELEASE.jar:1.3.2.RELEASE]
    at org.springframework.ldap.core.DistinguishedName.parse(DistinguishedName.java:217) [spring-ldap-core-1.3.2.RELEASE.jar:1.3.2.RELEASE]
    at org.springframework.ldap.core.DistinguishedName.(DistinguishedName.java:176) [spring-ldap-core-1.3.2.RELEASE.jar:1.3.2.RELEASE]
    at org.springframework.security.ldap.SpringSecurityLdapTemplate.searchForSingleEntryInternal(SpringSecurityLdapTemplate.java:208) [spring-security-ldap-3.2.0.RELEASE.jar:3.2.0.RELEASE]
    at org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.searchForUser(ActiveDirectoryLdapAuthenticationProvider.java:285) [spring-security-ldap-3.2.0.RELEASE.jar:3.2.0.RELEASE]
    at org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.doAuthentication(ActiveDirectoryLdapAuthenticationProvider.java:130) [spring-security-ldap-3.2.0.RELEASE.jar:3.2.0.RELEASE]
    at org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider.authenticate(AbstractLdapAuthenticationProvider.java:61) [spring-security-ldap-3.2.0.RELEASE.jar:3.2.0.RELEASE]
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:156) [spring-security-core-3.2.0.RELEASE.jar:3.2.0.RELEASE]
    --------------------------------------------------

    if naybody has any idea please share .....

    ReplyDelete
  22. @ Javin - Can you please mail me full XML and Java code for the 2nd solution

    ReplyDelete
  23. Hi Team where can i find complete end to end solution. I am new bee to this functionality can you kindly share complete code details

    ReplyDelete
  24. I am trying to authenticate users via LDAPS (second approach) it works however I get
    PartialResultException any ideas how to fix it ?

    ReplyDelete
  25. Good blogs, not just for this one, I read a few more, but for someone who wrote so many blogs, you have a lot of typos and misspelling

    ReplyDelete
  26. Thanks a lot!!! I got problems with Spring LDAP in AD and finally found my mistake by reading your wonderful post!

    ReplyDelete
  27. How to configure multiple search bases?

    ReplyDelete
  28. Where have you initialized ldapTemplate

    ReplyDelete
  29. Where is the source code of this example available. I'm quite interested in seeing InstallCert.java code.
    Please provide

    ReplyDelete
  30. Hi, I am new in this tecchnology. After declared this configuartion in xml file, what will be the next step? can you please explain in details?

    ReplyDelete
  31. Hello Nazibur, you need to include spring security JAR files and once you declare these XML and load spring security configuration file, all done, provided you have got your LDAP configuration right. If you are not sure how to enable Spring security, check my step by step tutorial to enable Spring Security in Java first.

    ReplyDelete
  32. Hey...Thanks for the great explanation..
    But I have the same exception that you have solved using the ldapTemplate.setIgnorePartialResultException(true);
    But I ama using the spring security using the annotation I am getting the same error can you please tell me how can I resolve this same error in this case.
    Thanks

    ReplyDelete