1

I am attempting to connect an application to LDAP after upgrading to: OpenJDK 17, SpringBoot 3.0.2, Gradle 8.0 Previously, we had a homegrown solution to connect to the active directory that will no longer function as written. My attempts have been met with a combination of this 500 error and a 401 Unauthorized error to the effect that I am not passing credentials to be authenticated against. Most of the examples or guides I've viewed have shown embedded examples which I can get to work fine but when moving those to the external source I'm met with the errors. Is there something in my configuration that just jumps out as wrong?

My final goal is a lightweight connection to the LDAP that authenticates against users in the given directories, the code has been somewhat sanitized - please let me know if additional information would be helpful.

I appreciate any help with this, thank you in advance.

{
    "timestamp": "2023-03-22T18:56:54.934+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "trace": "java.lang.NoSuchMethodError: 'java.util.Map jakarta.servlet.SessionCookieConfig.getAttributes()'\r\n\tat org.apache.catalina.core.ApplicationSessionCookieConfig.createSessionCookie(ApplicationSessionCookieConfig.java:238)\r\n\tat org.apache.catalina.connector.Request.doGetSession(Request.java:3060)\r\n\tat org.apache.catalina.connector.Request.getSession(Request.java:2450)\r\n\tat org.apache.catalina.connector.RequestFacade.getSession(RequestFacade.java:896)\r\n\tat org.apache.catalina.connector.RequestFacade.getSession(RequestFacade.java:908)\r\n\tat jakarta.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:229)\r\n\tat jakarta.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:229)\r\n\tat org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.saveToken(HttpSessionCsrfTokenRepository.java:58)\r\n\tat org.springframework.security.web.csrf.LazyCsrfTokenRepository$SaveOnAccessCsrfToken.saveTokenIfNecessary(LazyCsrfTokenRepository.java:235)\r\n\tat org.springframework.security.web.csrf.LazyCsrfTokenRepository$SaveOnAccessCsrfToken.getToken(LazyCsrfTokenRepository.java:192)\r\n\tat org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler.resolveCsrfTokenValue(XorCsrfTokenRequestAttributeHandler.java:73)\r\n\tat org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:120)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)\r\n\tat org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82)\r\n\tat org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233)\r\n\tat org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191)\r\n\tat org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:351)\r\n\tat org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:177)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:859)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1734)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.base/java.lang.Thread.run(Thread.java:833)\r\n",
    "message": "'java.util.Map jakarta.servlet.SessionCookieConfig.getAttributes()'",
    "path": "/monitor/stop"
}

Current configuration class follows...

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Value("${}")
    private String[] readAuthorization;
    @Value("${}")
    private String[] writeAuthorization;
    @Value("${ldap.context.source.userDn}")
    private String userDnPattern;
    @Value("${ldap.context.source.url}")
    private String ldapContextSourceUrl;
    @Value("${ldap.context.source.base}")
    private String ldapContextSourceBase;
    @Value("${ldap.context.source.password}")
    private String ldapContextSourcePassword;
    @Value("${ldap.context.source.referral}")
    private String ldapContextSourceReferral;
    @Value("${ldap.context.source.pooled}")
    private String ldapContextSourcePooled;

    @Autowired
    public LdapContextSource contextSource() {
        LdapContextSource ldapContextSource = new LdapContextSource();
        ldapContextSource.setUrl(ldapContextSourceUrl);
        ldapContextSource.setBase(ldapContextSourceBase);
        ldapContextSource.setUserDn(userDnPattern);
        ldapContextSource.setPassword(ldapContextSourcePassword);
        ldapContextSource.setReferral(ldapContextSourceReferral);
        ldapContextSource.afterPropertiesSet();
        return ldapContextSource;
    }

    @Bean
    AuthenticationManager ldapAuthenticationManager(BaseLdapPathContextSource contextSource) {
        LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
        factory.setUserDnPatterns(userDnPattern);
        factory.setUserDetailsContextMapper(new PersonContextMapper());
        return factory.createAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .requestMatchers("/**")
                .permitAll();
        return http.build();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web
                .ignoring()
                .requestMatchers("/readiness", "/liveness");
    }
}

Dependencies

// ==[ PLUGINS ]===============================================================
apply plugin: "org.owasp.dependencycheck"
apply plugin: 'java'
apply plugin: 'maven-publish'
if ( System.getenv().containsKey("JTEST_HOME") ) {
    apply from: System.getenv('JTEST_HOME') + '/integration/gradle/jtest.gradle'
}

// ==[ DEPENDENCIES ]==========================================================
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-ldap'
    implementation 'org.springframework.security:spring-security-ldap'
    implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-data-rest'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.elasticsearch.client:elasticsearch-rest-client:7.17.4'
    implementation 'org.elasticsearch:elasticsearch:7.17.4'
    implementation 'org.elasticsearch.client:elasticsearch-rest-high-level-client:7.17.4'
    implementation 'org.apache.commons:commons-lang3:3.8.1'
    implementation 'com.google.code.gson:gson:2.10.1'
    implementation 'com.google.guava:guava:30.0-android'
    implementation 'jakarta.servlet:jakarta.servlet-api:5.0.0'
    implementation 'com.microsoft.sqlserver:mssql-jdbc:10.2.0.jre17'

    developmentOnly 'org.springframework.boot:spring-boot-devtools'

    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.8.2'

    testImplementation 'junit:junit:4.13.1'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

kkellogg
  • 11
  • 2
  • `implementation 'jakarta.servlet:jakarta.servlet-api:5.0.0'` --> this is not correct. This is supposed to be **provided** by the target runtime such as Tomcat. I don't do Gradle, but in Maven terms you'd use `provided` on this in order to prevent it from ending up in the runtime classpath (and thereby colliding with the server-provided one of a newer version). The error you got is namely referring to [a method which was only introduced in Servlet API 6.0](https://jakarta.ee/specifications/servlet/6.0/apidocs/jakarta.servlet/jakarta/servlet/sessioncookieconfig#getAttributes()). – BalusC Apr 05 '23 at 12:51
  • If you understand Maven, then will be very helpful, https://stackoverflow.com/a/65704617, especially the "In any case" section near bottom of the answer. – BalusC Apr 05 '23 at 12:55
  • @BalusC if you change your comment to an answer I will select it - this was my main issue. – kkellogg Apr 25 '23 at 13:09

1 Answers1

0

I struggled also many hours with the ldap Security Config in Spring Boot 3. Now I finally succeeded. Let me share my solution.

@Configuration
public class MySecurityConfig {

    @Bean
    protected SecurityFilterChain filterChainQWeb(HttpSecurity http) throws Exception {
        return http
                .authorizeHttpRequests()
                .anyRequest().authenticated()
                .and()
                .httpBasic(Customizer.withDefaults())
                .build();
    }


    @Bean
    AuthenticationManager myAuthenticationManagerBean(LdapContextSource contextSource) {
        LdapBindAuthenticationManagerFactory factory =
                new LdapBindAuthenticationManagerFactory(contextSource);
        factory.setUserSearchBase("OU=myusers");
        factory.setUserSearchFilter("(&(objectClass=person)(cn={0}))");

        DefaultLdapAuthoritiesPopulator defaultAuthoritiesPopulator = new DefaultLdapAuthoritiesPopulator(
                contextSource, "OU=mygroups");
        defaultAuthoritiesPopulator.setGroupSearchFilter("(&(member={0})(cn=*))");
        defaultAuthoritiesPopulator.setGroupRoleAttribute("cn");
        factory.setLdapAuthoritiesPopulator(defaultAuthoritiesPopulator);

        return factory.createAuthenticationManager();
    }

}

In the application.properties

spring.ldap.base=DC=ciit,DC=at
spring.ldap.password=XXXX
spring.ldap.template.ignore-partial-result-exception=true
spring.ldap.urls=ldap://10.12.1.20:389
spring.ldap.username=cn=Administrator,cn=Users,DC=ciit,DC=at

logging.level.org.springframework.security=DEBUG

build.gradle

...    
    implementation 'org.springframework.boot:spring-boot-starter-data-ldap'
    implementation("org.springframework.security:spring-security-ldap")
    implementation 'org.springframework.boot:spring-boot-starter-security'
...

Hope this helps.

Michael
  • 192
  • 1
  • 5