spring-projects / spring-ldap

Spring LDAP
https://spring.io/spring-ldap
Apache License 2.0
342 stars 481 forks source link

Using Spring Boot 2.0.6 with Spring Data LDAP and openjdk #501

Open deanabadie opened 5 years ago

deanabadie commented 5 years ago

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?

rwinch commented 5 years ago

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

deanabadie commented 5 years ago

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.

rwinch commented 5 years ago

@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

deanabadie commented 5 years ago

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

Thanks.

deanabadie commented 5 years ago

@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 commented 5 years ago

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

vakninr commented 5 years ago

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"

rgabbard commented 4 years ago

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

rwinch commented 4 years ago

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.

vakninr commented 4 years ago

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.

davidkarlsen commented 4 years ago

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 commented 4 years ago

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.

rwinch commented 4 years ago

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

davidkarlsen commented 4 years ago

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.

rwinch commented 4 years ago

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.

davidkarlsen commented 4 years ago

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

aciokler commented 4 years ago

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

THD-Thomas-Lang commented 2 years ago

Are there any updates on this?

jzheaux commented 2 years ago

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?

benallard commented 7 months ago

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 commented 1 week ago

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);
    }
}