Java Spring 使用 Kerberos 和 LDAP 的 SSO 授权

Java Spring SSO authorization using Kerberos and LDAP

我正在使用 Java Spring Kerberos 安全库(link)开发基于 Linux 的 Rest API 应用程序。

我已经成功实现了 SSO 身份验证,它按预期工作,但现在需要添加 LDAP 集成以实现基于 ROLE 的授权。

但是,LDAP binding/search 不起作用 - SearchFilter 失败并出现以下异常:

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

申请详情:

1.安全-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})"

app.service-principal=myprincipal@INTRANET.EXAMPLE.COM

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

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

3。 LDAP DN 值

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

CN 值包含全名而不是 user/principal ID (myprincipal@INTRANET.EXAMPLE.COM)。我相信由于这个 LDAP search/binding 没有正常发生:

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'

问题

能否请您帮助理解问题并修改 CustomUserDetailsS​​ervice 以纠正此问题?

adAuthenticationProvider 使用登录密码身份验证单独按预期工作,但我不确定如何将其集成到 Kerberos 身份验证管理器中 - kerberosServiceAuthenticationProvider ,它们之间有联系。

kerberosServiceAuthenticationProvider 通过虚拟用户详细信息服务实现按预期工作。但是,只要我用 LDAP 实现替换它,它就会一直失败并显示上述错误消息。

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

}

感谢您的帮助。

解决方案非常简单 - 删除以下行的 application.properties 配置下的引号:

之前

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

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

之后

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

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