Tuesday, November 15, 2011

LDAP Active Directory Authentication in Java Spring Security Example Tutorial

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 project we need to authenticate against active directory using ldap by credentials provided in login screen. Some time this simple task gets tricky because of various issues faced during implementation and integration and no standard way of doing ldap authentication. Java provides ldap support but in this article I will mostly talk about spring security because its my preferred Java framework for authentication, authorization and security related stuff. you can do same thing in Java by writing your own program for doing LDAP search and than 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 feature which is required by enterprise java application including SSL Security, encryption of passwords and session timeout facilities.

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 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 user in LDAP server e.g. Microsoft Active Directory.

Ou - Organization Unit

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

Search - LDAP search is an operation which is performed to retrieve Dn of 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 base for ldap search operation e.g. dc=Microsoft,dc=org"

If you want to know more about LDAP check this link it has detailed information on LDAP.

LDAP Authentication Active Directory Spring

Active Directory Authentication in Spring Security


ldap authentication active directory spring
There are two ways to implement active directory authentication using LDAP protocol in spring security, first way is programmatic and declarative way which requires some coding and some configuration and second way is an out of box solution from spring security which just require to configure ActireDirectoryAuthentication provider and you are done. we will see both approach but I suggest using second one because of its simplicity and easy to use feature.



Active Directory Authentication using LDAP in Spring Security -1


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

1) Configuring LDAP Server

<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-dn and password, Ldap authentication on active directory or any other ldap directory is performed in two steps first an LDAP search is performed to locate Dn(Distinguised Name) of user and than this Dn is used to perform LDAP Bind , if bind is successful than usre authentication is successful other wise it fails. Some people prefer remote compare of password  than LDAP bind, but LDAP bind is what you mostly end of doing. most of 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 here in manager-dn and manager-password.

In Summary now LDAP login will be done on these step
1) Your Service or application bind itself with LDAP using manager-dn and manager-password.
2) LDAP search for user to find UserDn
3) LDAP bind using UserDn

2) Configuring LDAP Authentication Provider
<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>

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 Active directory.

Now small piece of coding to pass userPrincipalName and authenticate 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 whole day figuring out when my application was repeatedly throwing javax.naming.PartialResultException: Unprocessed Continuation Reference(s)

you can also use sAMAccountName for searching user, both userPrincipalName and sAMAccountName are unique in Active directory. What is important here is that it has to be full name e.g. name@domain like jimmy@stockmarket.com.

authenticate() method will return true or false based on result of bind operation.

Active Directory Authentication using LDAP in Spring Security - 2

Second approach is much simpler and cleaner because it comes out of 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>

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


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


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 there solution 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 invalid format of username. it solved by providing full name e.g. jemmy@stockmarket.com



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 as LDAP browser and there are lot of open source LDAP browser available in web e.g. jexplorer. you can browse and see data inside
Active directory by using LDAP browser.


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 its not safe. one solution is to use ldaps( 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 ot use ""ldaps://stockmarket.com/". actually port for ldap is 339 and for ldaps is 636 but that's been taken care by spring in second approach, in 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


Reason of this Exception is simple, Certificate returns during SSL handshake is not signed by any trustsed Certification Authority(CA) which is configured in you JRE keysotre e.g Verisign, Thwate, goDaddy or entrust etc. Instead Server is sending certificate which is not known to JRE.

To solve this problem you need to add certificates returned by Server into JRE's keystore.


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 in 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, certificate is already trusted

you are done, now if you try authenticating against ldaps you will be succeed.

There are many other approach 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
will try my best to help you.


Other Java Tutorials

Difference between StringBuffer and StringBuilder in Java

21 comments :

Rishabh said...

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

Anonymous said...

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.

Jim said...

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.

Josheph said...

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 ?

Rohan said...

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.

Vidha said...

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.

Val Schuman said...

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!

Anonymous said...

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.

Anonymous said...

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.

Maulik Kayastha said...

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

Anonymous said...

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

Anonymous said...

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.

Javin @ String split Java said...

@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

Anonymous said...

@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.

Anonymous said...

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.

Srividhya said...

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..

Kapil said...

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.

Jason Berk said...

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

Anonymous said...

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?

Anonymous said...

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?

Patit Pawan Karmakar said...

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 .....

Post a Comment