The Shibboleth IdP V3 software has reached its End of Life and is no longer supported. This documentation is available for historical purposes only. See the IDP4 wiki space for current documentation on the supported version.

LDAPAuthnConfiguration

Current File(s): conf/authn/ldap-authn-config.xml

Format: Native Spring

V2 Legacy File(s): conf/handler.xml, conf/login.config

Overview

This back-end for the password authentication login flow uses native LDAP libraries for password-based authentication instead of using a JAAS module. The primary advantages are slightly better performance and more control over the process, such as the ability to extract detailed account status information from the directory during a login.

General Configuration

Configuring LDAP as a back-end requires that the right import is active in authn/password-authn-config.xml:

Import in authn/password-authn-config.xml
<import resource="ldap-authn-config.xml" />

The other imports must be commented or removed.

A large number of beans are defined in authn/ldap-authn-config.xml to configure this back-end, but in most cases the properties in ldap.properties can do most of the work needed. The beans can be used for very advanced cases where a higher degree of control is required.

A bean alias is also defined that instantiates the LDAP back-end action as the "ValidateUsernamePassword" step of the web flow. This must not be changed.

Basic Configuration

Authenticator Configuration

The idp.authn.LDAP.authenticator property controls the workflow for how authentication occurs against the LDAP directory:

anonSearchAuthenticator

Performs an anonymous search for the user's DN

bindSearchAuthenticator

Binds as a configured DN then searches for the user's DN

directAuthenticator

User DNs are of a known format. i.e. CN=user_name,ou=accounts,dc=domain,dc=edu. No DN search is performed.

adAuthenticator

Configuration that leverages the AD specific @domain.com format. No DN search is performed since AD supports binding directly with that user name.

Depending on the choice above, various other properties must be set (see the reference section below).

Connection Configuration

Use the following properties to configure basic connection information for the LDAP directory:

  • idp.authn.LDAP.ldapURL
  • idp.authn.LDAP.useStartTLS
  • idp.authn.LDAP.useSSL
  • idp.authn.LDAP.connectTimeout

A connection pool is used, and there are several properties used to configure pool behavior (see the reference below).

SSL Configuration

If StartTLS or SSL are used, a source of trust anchors must be configured to control certificate validation, using the idp.authn.LDAP.sslConfig property:

certificateTrust

Uses the idp.authn.LDAP.trustCertificates property to load a resource containing the trust anchors (such as a file of PEM-format certificates)

keyStoreTrust

Uses the idp.authn.LDAP.trustStore property to load a keystore containing the trust anchors

jvmTrust

Uses the default JVM trust anchors (the JVM-wide "cacerts" file)

Advanced Configuration

Use authn/ldap-authn-config.xml to perform advanced configuration. The beans in this file correspond to properties of the Ldaptive authentication interface (see http://www.ldaptive.org/docs/guide/authentication). If you want to completely alter the LDAP objects used, you may want to consider copying the file to create your own version, and modify the beans used.

Reference

Beans

Some of the most important beans defined in authn/ldap-authn-config.xml follow.

Many other beans are defined in the file subordinate to the beans below. You are free to modify them as you see fit. Only the first two beans below are actually used by the login flow; the rest are injected into the shibboleth.authn.LDAP.authenticator bean to make up its content.

Bean IDTypeDefaultFunction
shibboleth.authn.LDAP.authenticatororg.ldaptive.auth.Authenticator
Must be set to a bean of the appropriate type to configure the LDAP authentication library, normally a property-controlled alias for one of: anonSearchAuthenticator, bindSearchAuthenticator, directAuthenticator, or adAuthenticator
shibboleth.authn.LDAP.returnAttributesjava.util.Collection<String>
The collection of attribute names to return in the LDAP operation, normally set via property
shibboleth.X509ResourceCredentialConfigResource path
A parent bean for supplying Ldaptive CredentialConfig instances using Spring resources containing X.509 trust anchors
shibboleth.KeystoreResourceCredentialConfigResource path
A parent bean for supplying Ldaptive CredentialConfig instances using Spring resources containing a Java keystore
sslConfigorg.ldaptive.ssl.SslConfig
Must be set to a bean of the appropriate type to configure the TLS trust source, normally a property-controlled alias for one of jvmTrust, certificateTrust, keystoreTrust

Properties

A number of properties are found in ldap.properties to configure LDAP authentication. Most of the time, this is sufficient to deal with most configurations without having to delve into modifying any beans.

PropertyTypeDefaultFunction
idp.authn.LDAP.authenticatorEnumerationanonSearchAuthenticatorControls the workflow for how authentication occurs against the LDAP, one of: anonSearchAuthenticator, bindSearchAuthenticator, directAuthenticator, adAuthenticator
idp.authn.LDAP.ldapURLLDAP URI
Connection URI for LDAP directory
idp.authn.LDAP.useStartTLSBooleantrueWhether StartTLS should be used after connecting with LDAP alone
idp.authn.LDAP.useSSLBooleanfalseWhether SSL should be used directly
idp.authn.LDAP.connectTimeoutInteger3000 (PT3S) 3.3Connection timeout in milliseconds
idp.authn.LDAP.responseTimeout 3.3DurationPT3S
idp.authn.LDAP.sslConfigEnumerationcertificateTrustHow to establish trust in the server's TLS certificate, one of: jvmTrust, certificateTrust, or keyStoreTrust
idp.authn.LDAP.trustCertificatesResource path
A resource to load trust anchors from, usually a local file in %{idp.home}/credentials
idp.authn.LDAP.trustStoreResource path
A resource to load a Java keystore containing trust anchors, usually a local file in %{idp.home}/credentials
idp.authn.LDAP.returnAttributesComma-sep'd Strings
List of attributes to request during authentication
idp.authn.LDAP.baseDNString
Base DN to search against, used by anonSearchAuthenticator, bindSearchAuthenticator
idp.authn.LDAP.subtreeSearchBooleanfalseWhether to search recursively, used by anonSearchAuthenticator, bindSearchAuthenticator
idp.authn.LDAP.userFilterString
LDAP search filter, used by anonSearchAuthenticator, bindSearchAuthenticator
idp.authn.LDAP.bindDNString
DN to bind with during search, used by bindSearchAuthenticator
idp.authn.LDAP.bindDNCredentialString
Password to bind with during search, used by bindSearchAuthenticator
idp.authn.LDAP.dnFormatString
A formatting string to generate the user DNs to authenticate, used by directAuthenticator, adAuthenticator
idp.pool.LDAP.minSizeInteger3Minimum LDAP connection pool size
idp.pool.LDAP.maxSizeInteger10Maximum LDAP connection pool size
idp.pool.LDAP.validateOnCheckoutBooleanfalseWhether to validate connections when checking them out of the pool
idp.pool.LDAP.validatePeriodicallyBooleantrueWhether to validate connections in the background
idp.pool.LDAP.validatePeriodInteger300 (PT5M) 3.3Interval in seconds between validation, if idp.pool.LDAP.validatePeriodically is true
idp.pool.LDAP.prunePeriodInteger300 (PT5M) 3.3Interval in seconds between looking for idle connections to reduce the pool back to its minimum size
idp.pool.LDAP.idleTimeInteger600 (PT10M) 3.3Time in seconds that connections must be idle to be eligible for pruning
idp.pool.LDAP.blockWaitTimeInteger3000 (PT3S) 3.3Time in milliseconds to wait for a free connection in the pool
idp.pool.LDAP.failFastInitializeBooleanfalseWhether to fail initialization of the components using the LDAP connection pool if the pool can't be established, or treat as a connection failure later

Attribute Retrieval

LDAP attributes are returned as part of the authentication process and exposed in the LDAPResponseContext.

PropertyValueResult
idp.authn.LDAP.returnAttributesuid,

eduPersonAffiliation

Returns the uid and

eduPersonAffiliation attributes.

idp.authn.LDAP.returnAttributes*Returns all user attributes on the entry.
idp.authn.LDAP.returnAttributes*,+Returns all user and operational attributes on the entry.
idp.authn.LDAP.returnAttributes1.1No attribute returned. No search performed.


By default, attributes will be searched for using the same connection the user authenticated on. Therefore the user must have read on any attributes for those to be returned. If you need access to attributes that user does not have read access to, then you must configure a connection pool that is authorized to read. The following configuration demonstrates how to add a new connection pool for that purpose.

ldap-authn-config.xml
<!-- Modify the authenticator to use the entry resolver -->
<bean name="anonSearchAuthenticator" class="org.ldaptive.auth.Authenticator" p:entryResolver-ref="bindSearchEntryResolver">
...
 
<!-- Add an entry resolver to read attributes -->
<bean id="bindSearchEntryResolver" class="org.ldaptive.auth.PooledSearchEntryResolver" p:connectionFactory-ref="entryResolverPooledConnectionFactory" />
<bean id="entryResolverPooledConnectionFactory" class="org.ldaptive.pool.PooledConnectionFactory" p:connectionPool-ref="entryResolverConnectionPool" />
<bean id="entryResolverConnectionPool" class="org.ldaptive.pool.BlockingConnectionPool" parent="connectionPool" p:connectionFactory-ref="entryResolverConnectionFactory" p:name="entry-resolver-pool" />
<bean id="entryResolverConnectionFactory" class="org.ldaptive.DefaultConnectionFactory" p:connectionConfig-ref="entryResolverConnectionConfig" />
<bean id="entryResolverConnectionConfig" parent="connectionConfig" p:connectionInitializer-ref="entryResolverConnectionInitializer" />
<bean id="entryResolverConnectionInitializer" class="org.ldaptive.BindConnectionInitializer" p:bindDn="%{idp.authn.LDAP.entryResolver.bindDN}">
    <property name="bindCredential">
        <bean class="org.ldaptive.Credential">
            <constructor-arg value="%{idp.authn.LDAP.entryResolver.bindDNCredential}" />
        </bean>
    </property>
</bean>

Add the idp.authn.LDAP.entryResolver.bindDN and idp.authn.LDAP.entryResolver.bindDNCredential properties to conf/ldap.properties to complete the configuration.

Note: if you're using the bindSearchAuthenticator and those credentials can be reused for entry resolution, then this configuration can be shortened by wiring the bindPooledConnectionFactory to the entry resolver.

DN Resolution

Single Directory with multiple branches

Extensible Matching

If your directory supports extensible matching, this is easiest way to find users that are distributed over multiple branches.

PropertyValueResult

idp.authn.LDAP.userFilter

(&(|(ou:dn:=people)(ou:dn:=guests))(uid={user}))Returns the entry that matches uid on either the people or guests branch.

idp.authn.LDAP.baseDN

dc=example,dc=orgThe branch containing both ou=people and ou=guests.

idp.authn.LDAP.subtreeSearch

trueEnable subtree searching on the dc=example,dc=org branch.

Aggregate DN Resolver

This authenticator combines the results of multiple DN resolvers.

ldap-authn-config.xml
<bean name="aggregateAuthenticator" class="org.ldaptive.auth.Authenticator">
    <constructor-arg index="0" ref="aggregateDnResolver" />
    <constructor-arg index="1" ref="aggregateAuthHandler" />
</bean>
<bean id="aggregateDnResolver" class="org.ldaptive.auth.AggregateDnResolver">
    <constructor-arg index="0" ref="dnResolvers" />
</bean>
<bean id="aggregateAuthHandler" class="org.ldaptive.auth.AggregateDnResolver$AuthenticationHandler" p:authenticationHandlers-ref="authHandlers" />
<util:map id="dnResolvers">
    <entry key="filter1" value-ref="dnResolver1" />
    <entry key="filter2" value-ref="dnResolver2" />
</util:map>
<!-- Define two DN resolvers that use anonymous search against the same directory -->
<bean id="dnResolver1" class="org.ldaptive.auth.PooledSearchDnResolver" p:baseDn="%{idp.authn.LDAP.baseDN1}"
      p:subtreeSearch="%{idp.authn.LDAP.subtreeSearch:false}" p:userFilter="%{idp.authn.LDAP.userFilter1}"
      p:connectionFactory-ref="anonSearchPooledConnectionFactory" />
<bean id="dnResolver2" class="org.ldaptive.auth.PooledSearchDnResolver" p:baseDn="%{idp.authn.LDAP.baseDN2}"
      p:subtreeSearch="%{idp.authn.LDAP.subtreeSearch:false}" p:userFilter="%{idp.authn.LDAP.userFilter2}"
      p:connectionFactory-ref="anonSearchPooledConnectionFactory" />
<!-- Use the same authentication handler for both DN resolvers -->
<util:map id="authHandlers">
    <entry key="filter1" value-ref="authHandler" />
    <entry key="filter2" value-ref="authHandler" />
</util:map>

Add the idp.authn.LDAP.baseDN[12] and idp.authn.LDAP.userFilter[12] properties to conf/ldap.properties to complete the configuration. The key values used in dnResolvers and authHandlers can be anything, but must tie a single DN resolver to a single auth handler. By default an error will occur if more than (1) DN is resolved.

Multiple Directories

Aggregate DN Resolver

This authenticator combines the results of multiple DN resolvers that connect to multiple directories.

ldap-authn-config.xml
<bean name="aggregateAuthenticator" class="org.ldaptive.auth.Authenticator">
    <constructor-arg index="0" ref="aggregateDnResolver" />
    <constructor-arg index="1" ref="aggregateAuthHandler" />
</bean>
<bean id="aggregateDnResolver" class="org.ldaptive.auth.AggregateDnResolver">
    <constructor-arg index="0" ref="dnResolvers" />
</bean>
<bean id="aggregateAuthHandler" class="org.ldaptive.auth.AggregateDnResolver$AuthenticationHandler" p:authenticationHandlers-ref="authHandlers" />
<util:map id="dnResolvers">
    <entry key="directory1" value-ref="dnResolver1" />
    <entry key="directory2" value-ref="dnResolver2" />
</util:map>
<util:map id="authHandlers">
    <entry key="directory1" value-ref="authHandler1" />
    <entry key="directory2" value-ref="authHandler2" />
</util:map>
<!-- define DN resolvers and authentication handlers for each directory... -->
Complete example with DN resolvers and authentication handlers for bindSearch
<!-- Properties:
- idp.authn.LDAP.authenticator = aggregateAuthenticator
- idp.authn.LDAP.ldapURL1
- idp.authn.LDAP.ldapURL2
- idp.authn.LDAP.baseDN1
- idp.authn.LDAP.baseDN2
- idp.authn.LDAP.userFilter1
- idp.authn.LDAP.userFilter2
- idp.authn.LDAP.bindDN1
- idp.authn.LDAP.bindDN2
- idp.authn.LDAP.bindDNCredential1
- idp.authn.LDAP.bindDNCredential2
-->
<bean id="aggregateAuthenticator" class="org.ldaptive.auth.Authenticator"
      c:resolver-ref="aggregateDnResolver"
      c:handler-ref="aggregateAuthHandler" />

<!-- Aggregate DN resolution -->
<bean id="aggregateDnResolver" class="org.ldaptive.auth.AggregateDnResolver"
      c:resolvers-ref="dnResolvers"
      p:allowMultipleDns="true" />
<util:map id="dnResolvers">
    <entry key="directory1" value-ref="bindSearchDnResolver1" />
    <entry key="directory2" value-ref="bindSearchDnResolver2" />
</util:map>

<!-- DN resolver 1 -->
<bean id="bindSearchDnResolver1" class="org.ldaptive.auth.PooledSearchDnResolver"
      p:baseDn="#{'%{idp.authn.LDAP.baseDN1:undefined}'.trim()}"
      p:subtreeSearch="%{idp.authn.LDAP.subtreeSearch:false}"
      p:userFilter="#{'%{idp.authn.LDAP.userFilter1:undefined}'.trim()}"
      p:connectionFactory-ref="bindSearchPooledConnectionFactory1" />
<bean id="bindSearchPooledConnectionFactory1" class="org.ldaptive.pool.PooledConnectionFactory"
      p:connectionPool-ref="bindSearchConnectionPool1" />
<bean id="bindSearchConnectionPool1" class="org.ldaptive.pool.BlockingConnectionPool" parent="connectionPool"
      p:connectionFactory-ref="bindSearchConnectionFactory1"
      p:name="search-pool1" />
<bean id="bindSearchConnectionFactory1" class="org.ldaptive.DefaultConnectionFactory"
      p:connectionConfig-ref="bindSearchConnectionConfig1" />
<bean id="bindSearchConnectionConfig1" parent="connectionConfig"
      p:connectionInitializer-ref="bindConnectionInitializer1"
      p:ldapUrl="%{idp.authn.LDAP.ldapURL1}" />
<bean id="bindConnectionInitializer1" class="org.ldaptive.BindConnectionInitializer"
      p:bindDn="#{'%{idp.authn.LDAP.bindDN1:undefined}'.trim()}">
    <property name="bindCredential">
    <bean class="org.ldaptive.Credential" c:password="%{idp.authn.LDAP.bindDNCredential1:undefined}" />
    </property>
</bean>
<!-- DN resolver 2 -->
<bean id="bindSearchDnResolver2" class="org.ldaptive.auth.PooledSearchDnResolver"
      p:baseDn="#{'%{idp.authn.LDAP.baseDN2:undefined}'.trim()}"
      p:subtreeSearch="%{idp.authn.LDAP.subtreeSearch:false}"
      p:userFilter="#{'%{idp.authn.LDAP.userFilter2:undefined}'.trim()}"
      p:connectionFactory-ref="bindSearchPooledConnectionFactory2" />
<bean id="bindSearchPooledConnectionFactory2" class="org.ldaptive.pool.PooledConnectionFactory"
      p:connectionPool-ref="bindSearchConnectionPool2" />
<bean id="bindSearchConnectionPool2" class="org.ldaptive.pool.BlockingConnectionPool" parent="connectionPool"
      p:connectionFactory-ref="bindSearchConnectionFactory2"
      p:name="search-pool2" />
<bean id="bindSearchConnectionFactory2" class="org.ldaptive.DefaultConnectionFactory"
      p:connectionConfig-ref="bindSearchConnectionConfig2" />
<bean id="bindSearchConnectionConfig2" parent="connectionConfig"
      p:connectionInitializer-ref="bindConnectionInitializer2"
      p:ldapUrl="%{idp.authn.LDAP.ldapURL2}" />
<bean id="bindConnectionInitializer2" class="org.ldaptive.BindConnectionInitializer"
      p:bindDn="#{'%{idp.authn.LDAP.bindDN2:undefined}'.trim()}">
    <property name="bindCredential">
        <bean class="org.ldaptive.Credential" c:password="%{idp.authn.LDAP.bindDNCredential2:undefined}" />
    </property>
</bean>

<!-- Aggregate authentication -->
<bean id="aggregateAuthHandler" class="org.ldaptive.auth.AggregateDnResolver$AuthenticationHandler"
      p:authenticationHandlers-ref="authHandlers" />
<util:map id="authHandlers">
    <entry key="directory1" value-ref="authHandler1" />
    <entry key="directory2" value-ref="authHandler2" />
</util:map>

<!-- Authentication handler 1 -->
<bean id="authHandler1" class="org.ldaptive.auth.PooledBindAuthenticationHandler"
      p:connectionFactory-ref="bindPooledConnectionFactory1" />
<bean id="bindPooledConnectionFactory1" class="org.ldaptive.pool.PooledConnectionFactory"
      p:connectionPool-ref="bindConnectionPool1" />
<bean id="bindConnectionPool1" class="org.ldaptive.pool.BlockingConnectionPool" parent="connectionPool"
      p:connectionFactory-ref="bindConnectionFactory1"
      p:name="bind-pool1" />
<bean id="bindConnectionFactory1" class="org.ldaptive.DefaultConnectionFactory"
      p:connectionConfig-ref="bindConnectionConfig1" />
<bean id="bindConnectionConfig1" parent="connectionConfig"
      p:ldapUrl="%{idp.authn.LDAP.ldapURL1}" />
<!-- Authentication handler 2 -->
<bean id="authHandler2" class="org.ldaptive.auth.PooledBindAuthenticationHandler"
      p:connectionFactory-ref="bindPooledConnectionFactory2" />
<bean id="bindPooledConnectionFactory2" class="org.ldaptive.pool.PooledConnectionFactory"
      p:connectionPool-ref="bindConnectionPool2" />
<bean id="bindConnectionPool2" class="org.ldaptive.pool.BlockingConnectionPool" parent="connectionPool"
      p:connectionFactory-ref="bindConnectionFactory2"
      p:name="bind-pool2" />
<bean id="bindConnectionFactory2" class="org.ldaptive.DefaultConnectionFactory"
      p:connectionConfig-ref="bindConnectionConfig2" />
<bean id="bindConnectionConfig2" parent="connectionConfig"
      p:ldapUrl="%{idp.authn.LDAP.ldapURL2}" />

DN resolvers are invoked asynchronously, so all resolvers will be used for each authentication request.

Account State

The LDAP library used (ldaptive) has the ability to extract detailed account status information from the directory during a login. According to ldaptive documentation, no standard for exposing account state data has been universally adopted by LDAP vendors, but the ldaptive library uses a handler which encapsulates account state information. This state contains Warning and Error types that are common to the most popular policy implementations.

Handling account state with OpenLDAP

The OpenLDAP password policy overlay (ppolicy), provides various password policy features. In some cases it can be desirable to be able to inform users whether a login is failed because of invalid credentials, an expired password, a locked account or give password expiration warning. In that cases, OpenLDAP password policy overlay adds account state information in LDAP responses.

In the IdP, the LDAP authn backend can be configured to handle the account state. The file authn/ldap-authn-config.xml has comments with instructions. The detailed configuration is:


ldap-authn-config.xml
<!-- 1. Add some beans -->
<bean id="authenticationResponseHandler" class="org.ldaptive.auth.ext.PasswordPolicyAuthenticationResponseHandler" /> 
<bean id="authenticationControl"  class="org.ldaptive.control.PasswordPolicyControl" />

<!-- 2. Comment out the bean of the handler with the same id, used for Active Directory:  -->
<!-- <bean id="authenticationResponseHandler" class="org.ldaptive.auth.ext.ActiveDirectoryAuthenticationResponseHandler" /> -->
    
<!-- 3. In the existing authHandler bean definition, add the attribute p:authenticationControls-ref="authenticationControl": -->
<bean id="authHandler" class="org.ldaptive.auth.PooledBindAuthenticationHandler" p:connectionFactory-ref="bindPooledConnectionFactory" p:authenticationControls-ref="authenticationControl" />

<!-- 4. In the authenticator bean definition, add the attribute p:authenticationResponseHandlers-ref="authenticationResponseHandler". 
Given that there are multiple authenticators, this attribute must be added to the authenticator in use 
(defined in ldap.properties, in idp.authn.LDAP.authenticator), e.g. if anonSearchAuthenticator is used, the definition could be: -->
<bean name="anonSearchAuthenticator" class="org.ldaptive.auth.Authenticator" p:authenticationResponseHandlers-ref="authenticationResponseHandler" >

With that configuration and the default flow definitions, the user can be informed on the login page if the password is expired or is near expiration (messages can be customized in messages/authn-messages.properties).

Locked Accounts:

To inform the user of a locked account, some configuration is needed to detect the error code informed by the LDAP validation action.

In authn/password-authn-config.xml, add an entry to the message clasification rules in  <util:map id="shibboleth.authn.Password.ClassifiedMessageMap">

password-authn-config.xml
<entry key="AccountLocked">
    <list>
        <value>ACCOUNT_LOCKED</value>
    </list>
</entry>

This maps the error code to a AccountLocked event. It will run the empty user flow authn/conditions/account-locked and then pass control back to the form. With this configuration, the user gets the message "Your account is locked".

Debug information:

Details of the AuthenticationResponse received, including the password policy controls, can be viewed using the TRACE log level in the logger of name "net.shibboleth.idp" (edit logback.xml).       

Active Directory Configuration

The file authn/ldap-authn-config.xml comes with definitions to configure Active Directory authentication response handlers against a single directory.

Active Directory does not fully support extensible match rules (https://msdn.microsoft.com/en-us/library/cc223241.aspx).

Active Directory (by default) does not support anonymous queries (https://technet.microsoft.com/en-us/library/Cc755809%28v=WS.10%29.aspx).

Using an entry resolver to generate password expiration warnings

ldap-authn-config.xml
<!-- authenticator with entry resolver-->
<bean id="adAuthenticator" class="org.ldaptive.auth.Authenticator" p:entryResolver-ref="bindSearchEntryResolver" p:authenticationResponseHandlers-ref="authenticationResponseHandler" p:resolveEntryOnFailure="false">
    <constructor-arg index="0" ref="formatDnResolver" />
    <constructor-arg index="1" ref="authHandler" />
</bean>
 
<!-- authentication response handler that defines a password age (in milliseconds), which is the length of time until a password expires -->
<bean id="authenticationResponseHandler" class="org.ldaptive.auth.ext.ActiveDirectoryAuthenticationResponseHandler" ><constructor-arg value="%{idp.authn.LDAP.passwordAge}" /></bean>
 
<!-- Add an entry resolver to read the pwdLastSet, must be included in idp.authn.LDAP.returnAttributes -->
<bean id="bindSearchEntryResolver" class="org.ldaptive.auth.PooledSearchEntryResolver" p:connectionFactory-ref="entryResolverPooledConnectionFactory" />
<bean id="entryResolverPooledConnectionFactory" class="org.ldaptive.pool.PooledConnectionFactory" p:connectionPool-ref="entryResolverConnectionPool" />
<bean id="entryResolverConnectionPool" class="org.ldaptive.pool.BlockingConnectionPool" parent="connectionPool" p:connectionFactory-ref="entryResolverConnectionFactory" p:name="entry-resolver-pool" />
<bean id="entryResolverConnectionFactory" class="org.ldaptive.DefaultConnectionFactory" p:connectionConfig-ref="entryResolverConnectionConfig" />
<bean id="entryResolverConnectionConfig" parent="connectionConfig" p:connectionInitializer-ref="entryResolverConnectionInitializer" />
<bean id="entryResolverConnectionInitializer" class="org.ldaptive.BindConnectionInitializer" p:bindDn="%{idp.authn.LDAP.entryResolver.bindDN}">
    <property name="bindCredential">
        <bean class="org.ldaptive.Credential">
            <constructor-arg value="%{idp.authn.LDAP.entryResolver.bindDNCredential}" />
        </bean>
    </property>
</bean>
 


Example for two Active Directories with two DN Resolvers for each

This example uses directed BaseDN's with LDAP filters, and binds the queries.

ldap-authn-config.xml
<!-- Define Directory1 connection pool -->
    <bean id="adConnectionPool1" class="org.ldaptive.pool.BlockingConnectionPool" abstract="true"
        p:blockWaitTime="%{idp.pool.LDAP.blockWaitTime:3000}"
        p:poolConfig-ref="adPoolConfig1"
        p:pruneStrategy-ref="adPruneStrategy1"
        p:validator-ref="adSearchValidator1"
        p:failFastInitialize="%{idp.pool.LDAP.failFastInitialize:false}" />
    <bean id="adPoolConfig1" class="org.ldaptive.pool.PoolConfig"
        p:minPoolSize="%{idp.pool.LDAP.minSize:3}"
        p:maxPoolSize="%{idp.pool.LDAP.maxSize:10}"
        p:validateOnCheckOut="%{idp.pool.LDAP.validateOnCheckout:false}"
        p:validatePeriodically="%{idp.pool.LDAP.validatePeriodically:true}"
        p:validatePeriod="%{idp.pool.LDAP.validatePeriod:300}" />
    <bean id="adPruneStrategy1" class="org.ldaptive.pool.IdlePruneStrategy"
        p:prunePeriod="%{idp.pool.LDAP.prunePeriod:300}"
        p:idleTime="%{idp.pool.LDAP.idleTime:600}" />
    <bean id="adSearchValidator1" class="org.ldaptive.pool.SearchValidator" />
<!-- Directory1 connection pool settings -->
    <bean id="adConnectionConfig1" class="org.ldaptive.ConnectionConfig" abstract="true"
        p:ldapUrl="%{idp.authn.LDAP.ldapURL1}"
        p:useStartTLS="%{idp.authn.LDAP.useStartTLS1:true}"
        p:useSSL="%{idp.authn.LDAP.useSSL1:false}"
        p:connectTimeout="%{idp.authn.LDAP.connectTimeout1:3000}"
        p:sslConfig-ref="adSslConfig1" />
    <alias name="%{idp.authn.LDAP.sslConfig:certificateTrust}" alias="adSslConfig1" />
<!-- Define Directory2 connection pool -->
    <bean id="adConnectionPool2" class="org.ldaptive.pool.BlockingConnectionPool" abstract="true"
        p:blockWaitTime="%{idp.pool.LDAP.blockWaitTime:3000}"
        p:poolConfig-ref="adPoolConfig2"
        p:pruneStrategy-ref="adPruneStrategy2"
        p:validator-ref="adSearchValidator2"
        p:failFastInitialize="%{idp.pool.LDAP.failFastInitialize:false}" />
    <bean id="adPoolConfig2" class="org.ldaptive.pool.PoolConfig"
        p:minPoolSize="%{idp.pool.LDAP.minSize:3}"
        p:maxPoolSize="%{idp.pool.LDAP.maxSize:10}"
        p:validateOnCheckOut="%{idp.pool.LDAP.validateOnCheckout:false}"
        p:validatePeriodically="%{idp.pool.LDAP.validatePeriodically:true}"
        p:validatePeriod="%{idp.pool.LDAP.validatePeriod:300}" />
    <bean id="adPruneStrategy2" class="org.ldaptive.pool.IdlePruneStrategy"
        p:prunePeriod="%{idp.pool.LDAP.prunePeriod:300}"
        p:idleTime="%{idp.pool.LDAP.idleTime:600}" />
    <bean id="adSearchValidator2" class="org.ldaptive.pool.SearchValidator" />
<!-- Directory2 connection pool settings -->
    <bean id="adConnectionConfig2" class="org.ldaptive.ConnectionConfig" abstract="true"
        p:ldapUrl="%{idp.authn.LDAP.ldapURL3}"
        p:useStartTLS="%{idp.authn.LDAP.useStartTLS3:true}"
        p:useSSL="%{idp.authn.LDAP.useSSL3:false}"
        p:connectTimeout="%{idp.authn.LDAP.connectTimeout3:3000}"
        p:sslConfig-ref="adSslConfig2" />
    <alias name="%{idp.authn.LDAP.sslConfig:certificateTrust}" alias="adSslConfig2" />

<!-- ldap.properties "idp.authn.LDAP.authenticator = adAggregateAuthenticator" -->
    <bean id="adAggregateAuthenticator" class="org.ldaptive.auth.Authenticator"
        p:authenticationResponseHandlers-ref="adAuthenticationResponseHandler">
        <constructor-arg index="0" ref="adAggregateDnResolver" />
        <constructor-arg index="1" ref="adAggregateAuthHandler" />
    </bean>
    <bean id="adAuthenticationResponseHandler" class="org.ldaptive.auth.ext.ActiveDirectoryAuthenticationResponseHandler" />
    <bean id="adAggregateDnResolver" class="org.ldaptive.auth.AggregateDnResolver">
        <constructor-arg index="0" ref="adDnResolvers" />
    </bean>
    <bean id="adAggregateAuthHandler" class="org.ldaptive.auth.AggregateDnResolver$AuthenticationHandler"
        p:authenticationHandlers-ref="adAuthHandlers" />
    <util:map id="adDnResolvers">
        <entry key="directory1_filter1" value-ref="adDnResolver1" />
        <entry key="directory1_filter2" value-ref="adDnResolver2" />
        <entry key="directory2_filter3" value-ref="adDnResolver3" />
        <entry key="directory2_filter4" value-ref="adDnResolver4" />
    </util:map>
<!-- Define two DN resolvers that use bind search against the Directory1 directory -->
    <bean id="adDnResolver1" class="org.ldaptive.auth.PooledSearchDnResolver"
        p:baseDn="%{idp.authn.LDAP.baseDN1}"
        p:subtreeSearch="%{idp.authn.LDAP.subtreeSearch1:false}"
        p:userFilter="%{idp.authn.LDAP.userFilter1}"
        p:connectionFactory-ref="adBindSearchPooledConnectionFactory1" />
    <bean id="adDnResolver2" class="org.ldaptive.auth.PooledSearchDnResolver"
        p:baseDn="%{idp.authn.LDAP.baseDN2}"
        p:subtreeSearch="%{idp.authn.LDAP.subtreeSearch2:false}"
        p:userFilter="%{idp.authn.LDAP.userFilter2}"
        p:connectionFactory-ref="adBindSearchPooledConnectionFactory1" />
<!-- Define two DN resolvers that use bind search against the Directory2 directory -->
    <bean id="adDnResolver3" class="org.ldaptive.auth.PooledSearchDnResolver"
        p:baseDn="%{idp.authn.LDAP.baseDN3}"
        p:subtreeSearch="%{idp.authn.LDAP.subtreeSearch3:false}"
        p:userFilter="%{idp.authn.LDAP.userFilter3}"
        p:connectionFactory-ref="adBindSearchPooledConnectionFactory2" />
    <bean id="adDnResolver4" class="org.ldaptive.auth.PooledSearchDnResolver"
        p:baseDn="%{idp.authn.LDAP.baseDN4}"
        p:subtreeSearch="%{idp.authn.LDAP.subtreeSearch4:false}"
        p:userFilter="%{idp.authn.LDAP.userFilter4}"
        p:connectionFactory-ref="adBindSearchPooledConnectionFactory2" />
<!-- Define Directory1 Search-pool -->
    <bean id="adBindSearchPooledConnectionFactory1" class="org.ldaptive.pool.PooledConnectionFactory"
        p:connectionPool-ref="adBindSearchConnectionPool1" />
    <bean id="adBindSearchConnectionPool1" class="org.ldaptive.pool.BlockingConnectionPool"
        parent="adConnectionPool1"
        p:connectionFactory-ref="adBindSearchConnectionFactory1"
        p:name="adSearch-pool1" />
    <bean id="adBindSearchConnectionFactory1" class="org.ldaptive.DefaultConnectionFactory"
        p:connectionConfig-ref="adBindSearchConnectionConfig1" />
    <bean id="adBindSearchConnectionConfig1" 
        parent="adConnectionConfig1"
        p:connectionInitializer-ref="adBindConnectionInitializer1" />
    <bean id="adBindConnectionInitializer1" class="org.ldaptive.BindConnectionInitializer"
        p:bindDn="%{idp.authn.LDAP.bindDN1}">
        <property name="bindCredential">
            <bean class="org.ldaptive.Credential">
                <constructor-arg value="%{idp.authn.LDAP.bindDNCredential1}" />
            </bean>
        </property>
    </bean>
<!-- Define Directory2 Search-pool -->
    <bean id="adBindSearchPooledConnectionFactory2" class="org.ldaptive.pool.PooledConnectionFactory"
        p:connectionPool-ref="adBindSearchConnectionPool2" />
    <bean id="adBindSearchConnectionPool2" class="org.ldaptive.pool.BlockingConnectionPool"
        parent="adConnectionPool2"
        p:connectionFactory-ref="adBindSearchConnectionFactory2"
        p:name="adSearch-pool2" />
    <bean id="adBindSearchConnectionFactory2" class="org.ldaptive.DefaultConnectionFactory"
        p:connectionConfig-ref="adBindSearchConnectionConfig2" />
    <bean id="adBindSearchConnectionConfig2" 
        parent="adConnectionConfig2"
        p:connectionInitializer-ref="adBindConnectionInitializer2" />
    <bean id="adBindConnectionInitializer2" class="org.ldaptive.BindConnectionInitializer"
        p:bindDn="%{idp.authn.LDAP.bindDN3}">
        <property name="bindCredential">
            <bean class="org.ldaptive.Credential">
                <constructor-arg value="%{idp.authn.LDAP.bindDNCredential3}" />
            </bean>
        </property>
    </bean>
    <util:map id="adAuthHandlers">
        <entry key="directory1_filter1" value-ref="adAuthHandler1" />
        <entry key="directory1_filter2" value-ref="adAuthHandler1" />
        <entry key="directory2_filter3" value-ref="adAuthHandler2" />
        <entry key="directory2_filter4" value-ref="adAuthHandler2" />
    </util:map>
<!-- Use the same authentication handler for both Directory1 DN resolvers -->
    <bean id="adAuthHandler1" class="org.ldaptive.auth.PooledBindAuthenticationHandler"
        p:connectionFactory-ref="adBindPooledConnectionFactory1" />
    <bean id="adBindPooledConnectionFactory1" class="org.ldaptive.pool.PooledConnectionFactory"
        p:connectionPool-ref="adBindConnectionPool1" />
    <bean id="adBindConnectionPool1" class="org.ldaptive.pool.BlockingConnectionPool"
        parent="adConnectionPool1"
        p:connectionFactory-ref="adBindConnectionFactory1"
        p:name="adBind-pool1" />
    <bean id="adBindConnectionFactory1" class="org.ldaptive.DefaultConnectionFactory"
        p:connectionConfig-ref="adBindConnectionConfig1" />
    <bean id="adBindConnectionConfig1"
        parent="adConnectionConfig1" />
<!-- Use the same authentication handler for both Directory2 DN resolvers -->
    <bean id="adAuthHandler2" class="org.ldaptive.auth.PooledBindAuthenticationHandler"
        p:connectionFactory-ref="adBindPooledConnectionFactory2" />
    <bean id="adBindPooledConnectionFactory2" class="org.ldaptive.pool.PooledConnectionFactory"
        p:connectionPool-ref="adBindConnectionPool2" />
    <bean id="adBindConnectionPool2" class="org.ldaptive.pool.BlockingConnectionPool"
        parent="adConnectionPool2"
        p:connectionFactory-ref="adBindConnectionFactory2"
        p:name="adBind-pool2" />
    <bean id="adBindConnectionFactory2" class="org.ldaptive.DefaultConnectionFactory"
        p:connectionConfig-ref="adBindConnectionConfig2" />
    <bean id="adBindConnectionConfig2"
        parent="adConnectionConfig2" />

Add missing Active Directory account state errors

ValidateUsernamePasswordAgainstLDAP authentication response.  If adAuthenticator is the authenticator set in the idp.authn.LDAP.authenticator property, Short Description values are contained in the response.  If bindSearchAuthenticator  is the authenticator set in the idp.authn.LDAP.authenticator property, HEX values are contained in the response. It is possible to use either or both as outlined in the following example. (http://ldapwiki.willeke.com/wiki/Common%20Active%20Directory%20Bind%20Errors)

password-authn-config.xml
   ...
        <entry key="AccountDisabled">
            <list>
                <value>ACCOUNT_DISABLED</value>
                <value>533</value>
            </list>
        </entry>
        <entry key="AccountExpired">
            <list>
                <value>ACCOUNT_EXPIRED</value>
            </list>
        </entry>
        <entry key="AccountLocked">
            <list>
                <value>ACCOUNT_LOCKED_OUT</value>
                <value>775</value>
            </list>
        </entry>
        <entry key="ChangePassword">
            <list>
                <value>PASSWORD_EXPIRED</value>
                <value>PASSWORD_MUST_CHANGE</value>
                <value>532</value>
            </list>
        </entry>
   ...
authn-messages.properties
...
AccountDisabled = account-disabled
AccountExpired = account-expired
AccountLocked = account-locked
ChangePassword = change-password

account-disabled.message = Your account is disabled.
account-expired.message = Your account has expired.
account-locked.message = Your account is locked.
change-password.message = You must change your password before authenticating here.
...

Notes

TBD