spring-ldap icon indicating copy to clipboard operation
spring-ldap copied to clipboard

Using Spring Boot 2.0.6 with Spring Data LDAP and openjdk

Open deanabadie opened this issue 6 years ago • 22 comments

I have a Spring Boot 2.0.6 application with spring-data-ldap 2.0.9.RELEASE, and I always get an java.lang.ClassCastException: java.naming/com.sun.jndi.ldap.LdapCtx cannot be cast to org.springframework.ldap.core.DirContextOperations exception when trying to retrieve all the distinguishedNames from an Active Directory tree. This problem occurs only while using openjdk (I tried using openjdk-9.0.4 and openjdk-10.0.2).

I get the following stack trace:

2019-02-05 11:57:31,037 ERROR [Management-Server] [ForkJoinPool.commonPool-worker-9] LdapService: Problem Querying Ldap to get the OU tree: {} java.lang.ClassCastException: java.naming/com.sun.jndi.ldap.LdapCtx cannot be cast to org.springframework.ldap.core.DirContextOperations at org.springframework.ldap.core.LdapTemplate$34.mapFromContext(LdapTemplate.java:1843) ~[spring-ldap-core-2.3.2.RELEASE.jar!/:2.3.2.RELEASE] at org.springframework.ldap.core.ContextMapperCallbackHandler.getObjectFromNameClassPair(ContextMapperCallbackHandler.java:69) ~[spring-ldap-core-2.3.2.RELEASE.jar!/:2.3.2.RELEASE] at org.springframework.ldap.core.CollectingNameClassPairCallbackHandler.handleNameClassPair(CollectingNameClassPairCallbackHandler.java:50) ~[spring-ldap-core-2.3.2.RELEASE.jar!/:2.3.2.RELEASE] at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:367) ~[spring-ldap-core-2.3.2.RELEASE.jar!/:2.3.2.RELEASE] at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:309) ~[spring-ldap-core-2.3.2.RELEASE.jar!/:2.3.2.RELEASE] at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:642) ~[spring-ldap-core-2.3.2.RELEASE.jar!/:2.3.2.RELEASE] at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:578) ~[spring-ldap-core-2.3.2.RELEASE.jar!/:2.3.2.RELEASE] at org.springframework.ldap.core.LdapTemplate.find(LdapTemplate.java:1840) ~[spring-ldap-core-2.3.2.RELEASE.jar!/:2.3.2.RELEASE] at org.springframework.ldap.core.LdapTemplate.find(LdapTemplate.java:1861) ~[spring-ldap-core-2.3.2.RELEASE.jar!/:2.3.2.RELEASE] at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?] at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?] at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?] at java.lang.reflect.Method.invoke(Method.java:564) ~[?:?] at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:223) ~[spring-core-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.cloud.context.scope.GenericScope$LockedScopedProxyFactoryBean.invoke(GenericScope.java:494) ~[spring-cloud-context-2.0.1.RELEASE.jar!/:2.0.1.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.ldap.core.LdapTemplate$$EnhancerBySpringCGLIB$$30b3b8ad.find() ~[spring-ldap-core-2.3.2.RELEASE.jar!/:2.3.2.RELEASE] at org.springframework.data.ldap.repository.support.SimpleLdapRepository.findAll(SimpleLdapRepository.java:149) ~[spring-data-ldap-2.0.9.RELEASE.jar!/:2.0.9.RELEASE] at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?] at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?] at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?] at java.lang.reflect.Method.invoke(Method.java:564) ~[?:?] at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:377) ~[spring-data-commons-2.0.11.RELEASE.jar!/:2.0.11.RELEASE] at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:200) ~[spring-data-commons-2.0.11.RELEASE.jar!/:2.0.11.RELEASE] at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:641) ~[spring-data-commons-2.0.11.RELEASE.jar!/:2.0.11.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:605) ~[spring-data-commons-2.0.11.RELEASE.jar!/:2.0.11.RELEASE] at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:590) ~[spring-data-commons-2.0.11.RELEASE.jar!/:2.0.11.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59) ~[spring-data-commons-2.0.11.RELEASE.jar!/:2.0.11.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61) ~[spring-data-commons-2.0.11.RELEASE.jar!/:2.0.11.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at com.sun.proxy.$Proxy228.findAll(Unknown Source) ~[?:?] at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?] at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?] at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?] at java.lang.reflect.Method.invoke(Method.java:564) ~[?:?] at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:197) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139) ~[spring-tx-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at com.sun.proxy.$Proxy228.findAll(Unknown Source) ~[?:?] at com.compapp.server.service.ldap.LdapService.collectAllOUInLdap(LdapService.java:159) ~[classes!/:?] at com.compapp.server.service.ldap.LdapService.retrieveLdapTreeAsJsonObject(LdapService.java:68) ~[classes!/:?] at com.compapp.server.service.ldap.LdapService$$FastClassBySpringCGLIB$$d05a21c0.invoke() ~[classes!/:?] at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at com.compapp.server.aop.logging.LoggingAspect.logAround(LoggingAspect.java:47) ~[classes!/:?] at jdk.internal.reflect.GeneratedMethodAccessor376.invoke(Unknown Source) ~[?:?] at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?] at java.lang.reflect.Method.invoke(Method.java:564) ~[?:?] at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.0.10.RELEASE.jar!/:5.0.10.RELEASE] at com.compapp.server.service.ldap.LdapService$$EnhancerBySpringCGLIB$$40367ff1.retrieveLdapTreeAsJsonObject() ~[classes!/:?] at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700) ~[?:?] at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1692) ~[?:?] at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:283) ~[?:?] at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1603) ~[?:?] at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:175) ~[?:?] 2019-02-05 11:57:31,041 ERROR [Management-Server] [ForkJoinPool.commonPool-worker-9] LoggingAspect: Exception in com.compapp.server.service.ldap.LdapService.retrieveLdapTreeAsJsonObject() with cause = null

I went over the official Spring Boot 2.0.6 documentation and in the System Requirements section it states:

  1. System Requirements

Spring Boot 2.0.6.RELEASE requires Java 8 or 9 and Spring Framework 5.0.10.RELEASE or above.

In addition the official Spring Data LDAP official documentation states:

  1. Requirements Spring Data LDAP 2.x binaries requires JDK level 8.0 or later, Spring Framework 5.0.8.RELEASE or later, and Spring LDAP 2.3.2.RELEASE or later.

However this error doesn't occur if I use Java 1.8.0_181. Is there some compatibility issue with open-jdk that I am not aware of?

deanabadie avatar Feb 05 '19 15:02 deanabadie

Can you put together a minimal sample so I can take a look? I'd also check your environment. It sounds like for some reason OpenJDK is not using org.springframework.ldap.core.support.DefaultDirObjectFactory (which is set in the environment Map within Spring LDAP).

rwinch avatar Feb 06 '19 17:02 rwinch

Hi Rob,

Thanks for your response and for checking this out. I have attached a zip file with a sample: ldap-demo.zip

In order to configure the LDAP connection you need to fill in the following properties in the application.yml file:

ldap.url: ldap://{ip}:{port} ldap.userDn: (e.g. domain\admin) ldap.password: (e.g. Pa$$w0rd) ldap.treeRoot: (e.g. dc=my,dc=ad,dc=tree,dc=root)

By the way I went ahead and checked this on Java 9.0.4 as well and got the same error, so this may not be only an OpenJDK issue.

deanabadie avatar Feb 07 '19 14:02 deanabadie

@deanabadie Can you please update the sample to be self contained by pointing to an in memory LDAP instance. See https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-ldap-embedded

rwinch avatar Feb 07 '19 15:02 rwinch

@rwinch Please see the updated attached sample: ldap-demo.zip

Thanks.

deanabadie avatar Feb 07 '19 18:02 deanabadie

@rwinch FYI I tried running the demo with the embedded LDAP server and I can't reproduce the error (To be honest I am getting an empty tree, it looks like I am unable to get any data from the ldif file). However, when connecting to an actual LDAP server this error does occur. I have tried connecting to multiple LDAP servers and I was able to reproduce this problem on all attempts.

deanabadie avatar Feb 10 '19 09:02 deanabadie

@rwinch I was able to reproduce the error with the embedded LDAP server, please see updated zip file: ldap-demo.zip

deanabadie avatar Feb 10 '19 16:02 deanabadie

Also not working with OpenJDK10 and OpenJDK11, the code is working while running it from the IDE "gradlew bootRun" but failed when compiling and running the jar itself. using "gradlew build" and then run with "Java -jar"

vakninr avatar Feb 12 '19 17:02 vakninr

Is there any update on this issue or has anyone found an acceptable workaround?

rgabbard avatar Feb 29 '20 20:02 rgabbard

I haven't sorted it out, but it is likely due to the DefaultDirObjectFactory not being used. I'd try and see if there were changes in JDK 9 on what factory is used.

rwinch avatar Mar 02 '20 13:03 rwinch

Actually that happened to us with OpenJdk we just switched to other distribution (amazon corretto 8 and it worked). Didn't invest much time in identifying the root cause.

vakninr avatar Mar 02 '20 16:03 vakninr

Seeing the same - but I think it might be connected with fat-jar packaging: https://github.com/spring-projects/spring-boot/issues/20666 - it runs fine in IntellJ on same jdk (but different OS)

davidkarlsen avatar Mar 24 '20 20:03 davidkarlsen

in my case the problem was threading: https://stackoverflow.com/questions/45742638/classcastexception-while-searching-for-ldap-user - so I guess it's really a classloader related bug in spring-ldap.

davidkarlsen avatar Mar 24 '20 22:03 davidkarlsen

@davidkarlsen In the stacktrace you mentioned it says:

java.lang.ClassCastException: com.sun.jndi.ldap.LdapCtx cannot be cast to org.springframework.ldap.core.DirContextAdapter

This is true. com.sun.jndi.ldap.LdapCtx is not an instance of org.springframework.ldap.core.DirContextAdapter.

Spring LDAP expects to work with DirContextAdapter which should be created with DefaultDirObjectFactory, but it appears that for whatever reason certain JDKs are not finding using DefaultDirObjectFactory. I'd look and see what if anything changed on how that is configured.

rwinch avatar Mar 24 '20 22:03 rwinch

It's due to the threadclassloader, see the SO thread I linked to. I ran it from a parallel stream and it failed with the classcast - making it a normal serial stream and it works.

davidkarlsen avatar Mar 25 '20 00:03 davidkarlsen

Can you explain more details on why the Thread ClassLoader is causing it? In the sample provided above, the ClassLoader is fixed but the JVM changes.

rwinch avatar Mar 25 '20 13:03 rwinch

IDK - the sample wasn't mine - but I can tell that it works as long as I run things serially.

davidkarlsen avatar Mar 25 '20 17:03 davidkarlsen

using parallel streams gives this error. Any reason why this would be the case? Can parallel stream support be added? Thanks.

aciokler avatar May 16 '20 21:05 aciokler

Are there any updates on this?

THD-Thomas-Lang avatar Feb 11 '22 11:02 THD-Thomas-Lang

Just catching up on this issue. I notice that the sample does not come with any build configuration, app configuration, or tests. It will help me go faster if the sample includes these. For example, it would be nice if there were a test (or repro steps) that demonstrate the behavior. Can someone either update the earlier sample with these or provide a new one?

jzheaux avatar Jul 19 '22 20:07 jzheaux

I (finally) updated my spring-security dependencies, and LDAP Logins stopped working. My test is working fine, but when running under the application server (tomcat), the different classloader makes it so that the ClassCastException is hurting me.

the error trace starts with:

java.lang.ClassCastException: class com.sun.jndi.ldap.LdapCtx cannot be cast to class org.springframework.ldap.core.DirContextAdapter (com.sun.jndi.ldap.LdapCtx is in module java.naming of loader 'bootstrap'; org.springframework.ldap.core.DirContextAdapter is in unnamed module of loader org.apache.catalina.loader.ParallelWebappClassLoader @43b3dac7)
        at org.springframework.security.ldap.SpringSecurityLdapTemplate.searchForSingleEntryInternal(SpringSecurityLdapTemplate.java:279) ~[spring-security-ldap-5.8.9.jar:5.8.9]
        at org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.searchForUser(ActiveDirectoryLdapAuthenticationProvider.java:324) ~[spring-security-ldap-5.8.9.jar:5.8.9]
        at org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.doAuthentication(ActiveDirectoryLdapAuthenticationProvider.java:168) ~[spring-security-ldap-5.8.9.jar:5.8.9]
        at org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider.authenticate(AbstractLdapAuthenticationProvider.java:79) ~[spring-security-ldap-5.8.9.jar:5.8.9]

My usage is pretty standard (or so i thought): I'm creating am ActiveDirectoryLdapAuthenticationProvider, and calling the authenticate method with a manually created UsernamePasswordAuthenticationToken. I don't really know what could I do wrong there.

benallard avatar Jan 26 '24 08:01 benallard

The following AuthenticationProvider wrapper solved the issue for me:

public class ClassLoaderAuthenticationProviderWrapper implements AuthenticationProvider {

    private final AuthenticationProvider itsAuthenticationProvider;

    public ClassLoaderAuthenticationProviderWrapper(AuthenticationProvider aAuthenticationProvider) {
        itsAuthenticationProvider = aAuthenticationProvider;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(itsAuthenticationProvider.getClass().getClassLoader());
            return itsAuthenticationProvider.authenticate(authentication);
        } finally {
            Thread.currentThread().setContextClassLoader(originalClassLoader);
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return itsAuthenticationProvider.supports(authentication);
    }
}

benallard avatar Sep 05 '24 14:09 benallard