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.