LDAP Authentication with Multiple Search Bases

Suppose you want to do LDAP authentication (using the BindLdapAuthenticationHandler) against multiple branches of your LDAP tree.

Options for this are, in order of simplest to most involved:

  1. define a broader search base
  2. define a broader search base and a tighter filter
  3. define multiple authentication handler beans
  4. customize the CAS server source code

Option 1 - search base further up a shared tree

Specify a search base further up the tree such that all of your desired paths are included, since by default the search will include sub-trees.

So, whereas your current configuration might be something like

ldap.authentication.filter=sAMAccountName=%u  
ldap.authentication.basedn=ou=collegeofartsandsciences,dc=identity,dc=school,dc=edu 

If you wanted to search on both

ou=collegeofartsandsciences,dc=identity,dc=school,dc=edu

and

ou=dramaschool,dc=identity,dc=school,dc=edu 

 

then you could specify

ldap.authentication.basedn=dc=school,dc=edu 

if and only if it's okay to search across all of dc=school,dc=edu , potentially including

ou=someotherou,dc=someotherdc,dc=school,dc=edu

that is, including all sub-trees under dc=school,dc=edu

This option won't work if the sub-trees you'd like to include in the search don't share a common branch further up the tree.

This option only works if the scope on the BindLdapAuthenticationHandler is set to SearchControls.SUBTREE_SCOPE (which is the default value for BindLdapAuthenticationHandler scope.)

Option 2 - search base further up the shared tree coupled with a clever filter

This is option 1 except adding an or clause to the filter such that it only matches on the desired sub-trees.

So, for the example above, the filter becomes:

ldap.authentication.filter=(&(|(ou:=collegeofartsandsciences)(ou:=dramaschool))(sAMAccountName=%u)) 

It'll still search the whole tree specified in the broader ldap.authentication.basedn (dc=school,dc=edu in my example), but the filter will preclude matches outside of the collegeofartsandsciences and dramaschool ou's.

Like option 1, this option also only works if the scope on the BindLdapAuthenticationHandler is set to SearchControls.SUBTREE_SCOPE (which is the default value for BindLdapAuthenticationHandler scope.)

Option 3 - declare multiple authentication handlers

Whereas your deployerConfigContext.xml

configuration might currently declare only one LDAP authentication handler, it could declare two.

Currently

Currently it's probably something like this:

Enumerating the authentication handlers:

<property name="authenticationHandlers">
  <list>
    <bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
      p:httpClient-ref="httpClient"/>

    <ref bean="lppeEnabledLdapAuthenticationHandler"/>

  </list>
</property>
and defining the lppeEnabledLdapAuthenticationHandler:

 

<bean id="lppeEnabledLdapAuthenticationHandler"
  class="org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler"

  p:filter="${ldap.authentication.filter}"
  p:searchBase="${ldap.authentication.basedn}"
  p:contextSource-ref="contextSource"
  p:ignorePartialResultException="${ldap.authentication.ignorePartialResultException}">

  <property name="ldapErrorDefinitions">
    <list>
      <bean class="org.jasig.cas.adaptors.ldap.LdapErrorDefinition"
        p:ldapPattern="data 530"
         p:type="badHours"/>

      <bean class="org.jasig.cas.adaptors.ldap.LdapErrorDefinition"
        p:ldapPattern="data 533"
        p:type="accountDisabled"/>
...
    </list>
  </property>
</bean>

Two authentication handlers

Here's what it would look like

Enumerating the authentication handlers:

    <property name="authenticationHandlers">
      <list>
        <bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
          p:httpClient-ref="httpClient"/>

        <ref bean="artsAndSciencesAuthenticationHandler"/>
        <ref bean="dramaSchoolAuthenticationHandler"/>

      </list>
    </property>

And then you'd have two declarations of handler implementations, one for each of those bean names:

<bean id="artsAndSciencesAuthenticationHandler"
  class="org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler"
  
  p:filter="sAMAccountName=%u  "
  p:searchBase="ou=collegeofartsandsciences,dc=identity,dc=school,dc=edu"
  p:contextSource-ref="contextSource"
  p:ignorePartialResultException="${ldap.authentication.ignorePartialResultException}">

  <property name="ldapErrorDefinitions">
    <list>
      <bean class="org.jasig.cas.adaptors.ldap.LdapErrorDefinition"
        p:ldapPattern="data 530"
        p:type="badHours"/>
      
      ....
      
    </list>
  </property>
</bean>

and

<bean id="dramaSchoolAuthenticationHandler"
  class="org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler"
  
  p:filter="sAMAccountName=%u  "
  p:searchBase="ou=dramaschool,dc=identity,dc=school,dc=edu"
  p:contextSource-ref="contextSource"
  p:ignorePartialResultException="${ldap.authentication.ignorePartialResultException}">

  <property name="ldapErrorDefinitions">
    <list>
      <bean class="org.jasig.cas.adaptors.ldap.LdapErrorDefinition"
        p:ldapPattern="data 530"
        p:type="badHours"/>
      
      ....
      
    </list>
  </property>
</bean>

And you could still externalize those filter and searchBase values to properties, I've just inlined them here for clarity of example.

Option 4 - Code customization

Finally, whereas the current BindLdapAuthenticationHandler is only able to search one tree, it could be customized to, say, take a List of search bases in configuration, and then at around line 72 this search executor could get more sophisticated to try each of the configured search bases rather than just the one it currently tries.

Currently:

    new SearchExecutor() {

      public NamingEnumeration executeSearch(final DirContext context) 
        throws NamingException {
        
        return context.search(base, filter, searchControls);
      }
    },

New version would be something like:

    new SearchExecutor() {

      public NamingEnumeration executeSearch(final DirContext context) 
        throws NamingException {
        
        NamingEnumeration resultSet = null;
        
        for (String base : bases) {
          NamingEnumeration resultSet = context.search(base, filter, searchControls);
          if (resultSet.hasMore()) return resultSet;
        }
        return resultSet;
      }
    },

 

References

There's a cas-user@ email list thread on this topic.