I want to implement ldap authentication in an existing spring boot project for my company. So for that I am trying to implement one sample spring boot app first, in which I will authenticate a ldap user from my company ldap server. If it works, fine then I will implement the same code into my existing project and I want to achieve this using ldap bind authentication mechanism only.
Following is the code to authenticate a ldap user using bind authentication mechanism in spring security that I have written for my sample app:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>SampleApp2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>SampleApp2</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
<version>5.5.3</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
HomeController.java
package com.example.SampleApp2;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HomeController {
@GetMapping("/")
public String index() {
return "Welcome to the home page!";
}
}
WebSecurityConfig.java
package com.example.SampleApp2;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
public XyzEncryption xyzEncrption() { // custom password encoder which is used in company's ldap server to authenticate user, even though I didn't use it any where
return new XyzEncryption();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.formLogin();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.contextSource()
.url("ldap://in.xyz.com:389/DC=in,DC=xyz,DC=com")
.managerDn("CN=Rohit Sarkar,OU=Engg,OU=KSPL Users,DC=in,DC=xyz,DC=com") //User Dn by which I am binding the connection with server
.managerPassword("PasswordOfRohitSarkar")
.and()
.userSearchFilter("sAMAccountName=abdulg")
.userDnPatterns("CN=Abdul Gaffar,OU=Engg,OU=KSPL Users,DC=in,DC=xyz,DC=com"); // User dn which need to be authenticated from server
}
}
But getting error:
org.springframework.ldap.PartialResultException: Unprocessed Continuation Reference(s); nested exception is javax.naming.PartialResultException: Unprocessed Continuation Reference(s); remaining name ''
at org.springframework.ldap.support.LdapUtils.convertLdapException(LdapUtils.java:216) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:385) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:328) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:629) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:570) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
at org.springframework.security.ldap.SpringSecurityLdapTemplate.searchForMultipleAttributeValues(SpringSecurityLdapTemplate.java:197) ~[spring-security-ldap-5.5.3.jar:5.5.3]
at org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator.getGroupMembershipRoles(DefaultLdapAuthoritiesPopulator.java:223) ~[spring-security-ldap-5.5.3.jar:5.5.3]
at org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator.getGrantedAuthorities(DefaultLdapAuthoritiesPopulator.java:203) ~[spring-security-ldap-5.5.3.jar:5.5.3]
at org.springframework.security.ldap.authentication.LdapAuthenticationProvider.loadUserAuthorities(LdapAuthenticationProvider.java:197) ~[spring-security-ldap-5.5.3.jar:5.5.3]
at org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider.authenticate(AbstractLdapAuthenticationProvider.java:83) ~[spring-security-ldap-5.5.3.jar:5.5.3]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182) ~[spring-security-core-5.5.3.jar:5.5.3]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:201) ~[spring-security-core-5.5.3.jar:5.5.3]
at org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.attemptAuthentication(UsernamePasswordAuthenticationFilter.java:85) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:222) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:132) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.3.12.jar:5.3.12]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.3.12.jar:5.3.12]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.3.12.jar:5.3.12]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.3.12.jar:5.3.12]
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.3.12.jar:5.3.12]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [catalina.jar:8.5.70]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [catalina.jar:8.5.70]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.12.jar:5.3.12]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.3.12.jar:5.3.12]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [catalina.jar:8.5.70]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [catalina.jar:8.5.70]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.12.jar:5.3.12]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.3.12.jar:5.3.12]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [catalina.jar:8.5.70]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [catalina.jar:8.5.70]
at org.springframework.boot.web.servlet.support.ErrorPageFilter.doFilter(ErrorPageFilter.java:126) [spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.web.servlet.support.ErrorPageFilter.access$000(ErrorPageFilter.java:64) [spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.web.servlet.support.ErrorPageFilter$1.doFilterInternal(ErrorPageFilter.java:101) [spring-boot-2.5.6.jar:2.5.6]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.3.12.jar:5.3.12]
at org.springframework.boot.web.servlet.support.ErrorPageFilter.doFilter(ErrorPageFilter.java:119) [spring-boot-2.5.6.jar:2.5.6]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [catalina.jar:8.5.70]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [catalina.jar:8.5.70]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) [spring-web-5.3.12.jar:5.3.12]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.3.12.jar:5.3.12]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [catalina.jar:8.5.70]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [catalina.jar:8.5.70]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:196) [catalina.jar:8.5.70]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) [catalina.jar:8.5.70]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:544) [catalina.jar:8.5.70]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) [catalina.jar:8.5.70]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) [catalina.jar:8.5.70]
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:698) [catalina.jar:8.5.70]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) [catalina.jar:8.5.70]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:364) [catalina.jar:8.5.70]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:624) [tomcat-coyote.jar:8.5.70]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-coyote.jar:8.5.70]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:831) [tomcat-coyote.jar:8.5.70]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1650) [tomcat-coyote.jar:8.5.70]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-coyote.jar:8.5.70]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) [tomcat-util.jar:8.5.70]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) [tomcat-util.jar:8.5.70]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-util.jar:8.5.70]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_302]
Caused by: javax.naming.PartialResultException: Unprocessed Continuation Reference(s)
at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:3024) ~[na:1.8.0_302]
at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2998) ~[na:1.8.0_302]
at com.sun.jndi.ldap.LdapCtx.searchAux(LdapCtx.java:1874) ~[na:1.8.0_302]
at com.sun.jndi.ldap.LdapCtx.c_search(LdapCtx.java:1797) ~[na:1.8.0_302]
at com.sun.jndi.toolkit.ctx.ComponentDirContext.p_search(ComponentDirContext.java:392) ~[na:1.8.0_302]
at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(PartialCompositeDirContext.java:358) ~[na:1.8.0_302]
at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(PartialCompositeDirContext.java:341) ~[na:1.8.0_302]
at javax.naming.directory.InitialDirContext.search(InitialDirContext.java:267) ~[na:1.8.0_302]
at org.springframework.ldap.core.LdapTemplate$4.executeSearch(LdapTemplate.java:322) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:363) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
... 72 common frames omitted
I have used follwoing instead of hard coding:
.userSearchFilter("sAMAccountName={0}")
Still getting same error.
I have tried ad authentication provider to authenticate the user and its being authenticated successfully, but since it's not a standard way in spring security, I want to use bind authentication and don't want to use password compare authentication. I want to keep user's password hidden.
Following is the code to authenticate user using ad authentication provider:
package com.example.SampleApp2;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
public KovairEncryption kovairEncrption() {
return new KovairEncryption();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.formLogin();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
@Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider authenticationProvider =
new ActiveDirectoryLdapAuthenticationProvider("in.xyz.com", "ldap://in.xyz.com")
authenticationProvider.setConvertSubErrorCodesToExceptions(true);
authenticationProvider.setUseAuthenticationRequestCredentials(true);
return authenticationProvider;
}
}
I have also tried traditional java code to authenticate ad user it was also working fine but unable to authenticate using bind authentication mechanism. I have read lot of articles and have gone through lot of spring reference document but couldn't find any solution. I have already spent almost one week to get a proper solution.
OK. So after spending lot of times I got solutions.
First of all, there is nothing wrong with the code snippet mentioned above, reason of this error is-
Accroding to spring security doc under section 18.4.4, In case of active directory search, after authenticating the user successfully, the LdapAuthenticationProvider
will attempt to load a set of authorities for the user by calling the configured LdapAuthoritiesPopulator
bean. The DefaultLdapAuthoritiesPopulator
is an implementation which will load the authorities by searching the directory for groups of which the user is a member (typically these will be groupOfNames or groupOfUniqueNames entries in the directory).
If you want to use LDAP only for authentication, but load the authorities from a difference source (such as a database) then you can provide your own implementation of this interface and inject that instead.
But I was ignoring the authorities which was returning after successfull authentication of the user, that is why I was getting this error.
There are few ways to solve the problem:
1st Way: using Context.REFERRAL
to follow
Disadvantage: it will take lot of time to follow the referral and authenticate user
2nd Way: By implementing LdapAuthoritiesPopulator
CustomAuthoritiesPopulator.java
@Component
public class CustomAuthoritiesPopulator implements LdapAuthoritiesPopulator {
@Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations dco, String string) {
Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
grantedAuthorities.add(new SimpleGrantedAuthority("ADMIN"));
return grantedAuthorities;
}
}
WebSecurityConfig.java
@Autowired
CustomAuthoritiesPopulator authoritiespopulator;
// I have mentioned only configure() method here, others code will be as it is
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.contextSource()
.url("ServerUrl")
.managerDn("BindUserDN")
.managerPassword("BindUserPassword")
.and()
.ldapAuthoritiesPopulator(authoritiespopulator)
.userSearchFilter("sAMAccountName=UserName")
}
Note: This will be helpful if you want set some custom authority
3rd Way: Using .setIgnorePartialResultException(true)
WebSecurityConfig.java
@Bean
LdapContextSource ldapContextSource() {
LdapContextSource ldapContextSource = new LdapContextSource();
ldapContextSource.setUrl("ServerUrl");
ldapContextSource.setUserDn("BindUserDN");
dapContextSource.setPassword("BindUserPassword");
ldapContextSource.afterPropertiesSet();
}
@Bean
public LdapAuthoritiesPopulator ldapAuthoritiesPopulator() throws Exception {
DefaultLdapAuthoritiesPopulator authoritiespopulator= new DefaultLdapAuthoritiesPopulator(ldapContextSource(), "base");
authoritiespopulator.setIgnorePartialResultException(true);
return authoritiespopulator;
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.contextSource()
.url("ServerUrl")
.managerDn("BindUserDN")
.managerPassword("BindUserPassword")
.and()
.ldapAuthoritiesPopulator(authoritiespopulator)
.userSearchFilter("sAMAccountName=UserName")
}