2
votes

I am working on Linux based Rest API application using Java Spring Kerberos Security libraries (link).

I have managed to implement SSO authentication which works as expected, but now need to add LDAP integration in order to implement ROLE based authorization.

However, LDAP binding/search doesn't work - SearchFilter failing with following exception:

exception

org.springframework.ldap.InvalidSearchFilterException: invalid attribute description; nested exception is javax.naming.directory.InvalidSearchFilterException: invalid attribute description; remaining name 'dc=intranet,dc=example,dc=com'
    org.springframework.ldap.support.LdapUtils.convertLdapException(LdapUtils.java:135)
    org.springframework.ldap.core.LdapTemplate.executeWithContext(LdapTemplate.java:809)
    org.springframework.ldap.core.LdapTemplate.executeReadOnly(LdapTemplate.java:792)
    org.springframework.security.ldap.SpringSecurityLdapTemplate.searchForSingleEntry(SpringSecurityLdapTemplate.java:194)
    org.springframework.security.ldap.search.FilterBasedLdapUserSearch.searchForUser(FilterBasedLdapUserSearch.java:116)
    org.springframework.security.ldap.userdetails.LdapUserDetailsService.loadUserByUsername(LdapUserDetailsService.java:38)
    org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider.authenticate(KerberosServiceAuthenticationProvider.java:66)
    org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:156)
    org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter.doFilter(SpnegoAuthenticationProcessingFilter.java:145)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:199)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
    org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
    org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344)
    org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261)
root cause

javax.naming.directory.InvalidSearchFilterException: invalid attribute description; remaining name 'dc=intranet,dc=example,dc=com'
    com.sun.jndi.ldap.Filter.encodeSimpleFilter(Filter.java:446)
    com.sun.jndi.ldap.Filter.encodeFilter(Filter.java:171)
    com.sun.jndi.ldap.Filter.encodeFilterString(Filter.java:74)
    com.sun.jndi.ldap.LdapClient.search(LdapClient.java:546)
    com.sun.jndi.ldap.LdapCtx.doSearch(LdapCtx.java:1985)
    com.sun.jndi.ldap.LdapCtx.searchAux(LdapCtx.java:1844)
    com.sun.jndi.ldap.LdapCtx.c_search(LdapCtx.java:1769)
    com.sun.jndi.ldap.LdapCtx.c_search(LdapCtx.java:1786)
    com.sun.jndi.toolkit.ctx.ComponentDirContext.p_search(ComponentDirContext.java:418)
    com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(PartialCompositeDirContext.java:396)
    javax.naming.directory.InitialDirContext.search(InitialDirContext.java:297)
    org.springframework.security.ldap.SpringSecurityLdapTemplate.searchForSingleEntryInternal(SpringSecurityLdapTemplate.java:208)
    org.springframework.security.ldap.SpringSecurityLdapTemplate$3.executeWithContext(SpringSecurityLdapTemplate.java:196)
    org.springframework.ldap.core.LdapTemplate.executeWithContext(LdapTemplate.java:806)
    org.springframework.ldap.core.LdapTemplate.executeReadOnly(LdapTemplate.java:792)
    org.springframework.security.ldap.SpringSecurityLdapTemplate.searchForSingleEntry(SpringSecurityLdapTemplate.java:194)
    org.springframework.security.ldap.search.FilterBasedLdapUserSearch.searchForUser(FilterBasedLdapUserSearch.java:116)
    org.springframework.security.ldap.userdetails.LdapUserDetailsService.loadUserByUsername(LdapUserDetailsService.java:38)
    org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider.authenticate(KerberosServiceAuthenticationProvider.java:66)
    org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:156)
    org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter.doFilter(SpnegoAuthenticationProcessingFilter.java:145)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:199)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
    org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
    org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344)
    org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261)

Application details:

1. Security-context.xml

 <beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:sec="http://www.springframework.org/schema/security"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">


  <context:property-placeholder location="classpath:application.properties"/>

  <sec:http entry-point-ref="spnegoEntryPoint" use-expressions="true" >
    <sec:intercept-url pattern="/" access="permitAll" />
    <sec:intercept-url pattern="/home" access="permitAll" />
    <sec:intercept-url pattern="/login" access="permitAll" />
    <sec:intercept-url pattern="/test" access="authenticated"/>
    <sec:intercept-url pattern="/data" access="hasRole('ROLE_ADMIN')"/>
    <sec:form-login login-page="/login" />
    <sec:custom-filter ref="spnegoAuthenticationProcessingFilter"
      before="BASIC_AUTH_FILTER" />
  </sec:http>

  <sec:authentication-manager alias="authenticationManager">
    <sec:authentication-provider ref="kerberosServiceAuthenticationProvider" />
    <sec:authentication-provider ref="adAuthenticationProvider" />
  </sec:authentication-manager>


  <bean id="adAuthenticationProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
        <constructor-arg  value="${ldap.domain}"/>
        <constructor-arg  value="${ldap.url}"/>
        <property name="userDetailsContextMapper" ref="CustomUserDetailsContextMapper" /> 
  </bean>

  <bean id="spnegoEntryPoint"
    class="org.springframework.security.kerberos.web.authentication.SpnegoEntryPoint" >
  </bean>

  <bean id="spnegoAuthenticationProcessingFilter"
    class="org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter">
    <property name="authenticationManager" ref="authenticationManager" />
  </bean>

  <bean id="kerberosServiceAuthenticationProvider"
    class="org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider">
    <property name="ticketValidator">
      <bean
        class="org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidator">
        <property name="servicePrincipal" value="${app.service-principal}" />
        <property name="keyTabLocation" value="${app.keytab-location}" />
        <property name="debug" value="true" />
      </bean>
    </property>
    <property name="userDetailsService" ref="CustomUserDetailsService" />
  </bean>

  <bean id="authorizationContextSource" class="org.springframework.security.kerberos.client.ldap.KerberosLdapContextSource">
      <constructor-arg value="${ldap.url}" />
      <property name="loginConfig">
          <bean class="org.springframework.security.kerberos.client.config.SunJaasKrb5LoginConfig">
               <property name="servicePrincipal" value="${app.service-principal}" />
               <property name="keyTabLocation" value="${app.keytab-location}" />
               <property name="useTicketCache" value="false" />
               <property name="isInitiator" value="true" />
               <property name="debug" value="true" />
           </bean>
      </property>
    </bean>

    <bean id="CustomUserDetailsService" class="org.springframework.security.ldap.userdetails.LdapUserDetailsService">
            <constructor-arg index="0" ref="userSearch" />
            <constructor-arg index="1" ref="CustomLdapAuthoritiesPopulator" />
            <property name="userDetailsMapper" ref="CustomUserDetailsContextMapper" />
    </bean>

    <bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
            <constructor-arg index="0" value="${ldap.ldap-search-base}" />
            <constructor-arg index="1" value="${ldap.search-filter}" />
            <constructor-arg index="2" ref="authorizationContextSource" />
            <property name="searchSubtree" value="true" />
    </bean>

   <bean id="CustomUserDetailsContextMapper" class="com.my.utility.UserDetailsContextMapperImpl" />

   <bean id="CustomLdapAuthoritiesPopulator" class="com.my.utility.ActiveDirectoryLdapAuthoritiesPopulator" />

</beans>

2. application.properties

ldap.url=ldap://intranet.example.com

ldap.domain=intranet.example.com

ldap.ldap-search-base="DC=INTRANET,DC=EXAMPLE,DC=COM"

ldap.search-filter="(userPrincipalName={0})"

[email protected]

app.keytab-location=file:/apps/tomcat/myprincipal.keytab

app.krb5=file:/apps/tomcat/conf/krb5.conf

3. LDAP DN value

CN=Full Name,OU=Users,OU=LDN,OU=EMEA,OU=GLB,DC=INTRANET,DC=EXAMPLE,DC=com

CN value comprises Full Name and not user/principal ID ([email protected]). I believe due to this LDAP search/binding doesn't happen properly:

org.springframework.ldap.InvalidSearchFilterException: invalid attribute description; nested exception is javax.naming.directory.InvalidSearchFilterException: invalid attribute description; remaining name 'dc=intranet,dc=example,dc=com'

Question

Could you please help to understand the issue and amend CustomUserDetailsService to rectify this issue?

adAuthenticationProvider works as expected separately using login password authentication, but I am not sure how to integrate it into Kerberos authentication manager - kerberosServiceAuthenticationProvider, there are links between them.

kerberosServiceAuthenticationProvider works as expected with dummy user details service implementation. However, as long as I replace it with LDAP implementation it keeps failing with error message mentioned above.

public class DummyUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        return new User(username, "notUsed", true, true, true, true,
                AuthorityUtils.createAuthorityList("ROLE_USER"));
    }

}

Thank you for your help.

1

1 Answers

2
votes

Solution is very simple - remove quotes under application.properties config for following rows:

Before

ldap.ldap-search-base="DC=INTRANET,DC=EXAMPLE,DC=COM"

ldap.search-filter="(userPrincipalName={0})"

After

ldap.ldap-search-base=DC=INTRANET,DC=EXAMPLE,DC=COM

ldap.search-filter=(userPrincipalName={0})