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:
- define a broader search base
- define a broader search base and a tighter filter
- define multiple authentication handler beans
- 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.