title: Adding multiple ldap sources to spring-security in multi-domain environment
body: <p>I am trying to add a secondary ldap contextSource to spring security in a split domain environment and I seem to be coming up short. I realize similar questions have been asked before but this is for two separate domains logging into the same application. </p>
  
  <p>My first step was to add the secondary context source to my security-config xml file like so:</p>
  
  <pre><code>&lt;beans:bean id="secondaryContextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource"&gt;
      &lt;beans:constructor-arg value="ldap://&lt;ldap address&gt;:389/DC=example,DC=com"/&gt;
      &lt;beans:property name="userDn" value="CN=BindAccount,CN=Users,DC=example,DC=com" /&gt;
      &lt;beans:property name="password" value="examplepw" /&gt;
  &lt;/beans:bean&gt;
  </code></pre>
  
  <p>In addition I added the constructor-arg to the ldapAuthenticationProvider and replaced the BindAuthenticator with my custom class like so: </p>
  
  <pre><code>    &lt;beans:bean id="ldapAuthenticationProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider"&gt;    
      &lt;beans:constructor-arg&gt;
          &lt;beans:bean class="org.springframework.security.ldap.authentication.BindAuthenticator"&gt;
              &lt;beans:constructor-arg ref="contextSource" /&gt;
              &lt;beans:constructor-arg ref="secondaryContextSource" /&gt;
              &lt;beans:property name="userSearch"&gt;
                  &lt;beans:bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch"&gt;
                    &lt;beans:constructor-arg index="0" value="CN=Users"/&gt;
                    &lt;beans:constructor-arg index="1" value="(userPrincipalName={0})"/&gt;
                    &lt;beans:constructor-arg index="2" ref="contextSource" /&gt;
                  &lt;/beans:bean&gt;
              &lt;/beans:property&gt;
  
          &lt;/beans:bean&gt;
      &lt;/beans:constructor-arg&gt;       
       &lt;beans:property name="userDetailsContextMapper"&gt;
          &lt;beans:bean id="employeeServiceFacade" class="com.example.service.security.EmployeeServiceFacade" /&gt;
      &lt;/beans:property&gt;   
        &lt;beans:constructor-arg&gt;
          &lt;beans:bean class="com.example.web.security.CustomLdapAuthoritiesPopulator" /&gt;
      &lt;/beans:constructor-arg&gt;
  &lt;/beans:bean&gt;
  </code></pre>
  
  <p>Then I attempted to extend BindAuthenticator to accept and set a secondary context source in the constructor. Initially I couldn't get this to work so I completely rewrote the BindAuthenticator class and extended AbstractLdapAuthenticator to get around BindAuthenticator. Then I overrode the authenticate method to check if the username contained the secondary DN and if it did, I would call bindWithDn again to attempt to rebind to the secondary domain. This is where I think I am going about this all wrong because when it attempts to get the new Dn it fails. Basically it states that it could not bind to the domain. (I have triple checked the domain settings and have connected to it with a ldap administration console and took those settings for my app) Here is my extended BindAuthenticator</p>
  
  <pre><code>public class ExtendedBindAuthenticator extends AbstractLdapAuthenticator {
  
      private BaseLdapPathContextSource secondaryContextSource;
  
      public void setSecondContextSource(BaseLdapPathContextSource secondContextSource) {
          this.secondaryContextSource = secondaryContextSource;
      }
  
  
      public ExtendedBindAuthenticator(BaseLdapPathContextSource contextSource, BaseLdapPathContextSource secondContextSource) {
          super(contextSource);
          this.secondaryContextSource = secondaryContextSource;
      }
  
  
      public DirContextOperations authenticate(Authentication authentication) {
          DirContextOperations user = null;
          Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                  "Can only process UsernamePasswordAuthenticationToken objects");
  
          String username = authentication.getName();
          String password = (String)authentication.getCredentials();         
  
          if(username.contains("secondDomain")) {
              DirContextOperations secondaryUser = getUserSearch().searchForUser(username);
              this.bindWithDn(secondaryUser.getDn().toString(), username, password);
  
          }
  
  
          if (!StringUtils.hasLength(password)) {
  
              throw new BadCredentialsException(messages.getMessage("BindAuthenticator.emptyPassword",
                      "Empty Password"));
          }
  
          // If DN patterns are configured, try authenticating with them directly
          for (String dn : getUserDns(username)) {
              user = this.bindWithDn(dn, username, password);
  
              if (user != null) {
                  break;
              }
          }
  
          // Otherwise use the configured search object to find the user and authenticate with the returned DN.
          if (user == null &amp;&amp; getUserSearch() != null) {
              DirContextOperations userFromSearch = getUserSearch().searchForUser(username);
              user = bindWithDn(userFromSearch.getDn().toString(), username, password);
          }
  
          if (user == null) {
              throw new BadCredentialsException(
                      messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
          }
  
          return user;
      }
  
      private DirContextOperations bindWithDn(String userDnStr, String username, String password) {
          BaseLdapPathContextSource ctxSource = (BaseLdapPathContextSource) getContextSource();
  
          if(username.contains("secondDomain")) {
              ctxSource = secondaryContextSource;
          }
  
          DistinguishedName userDn = new DistinguishedName(userDnStr);
          DistinguishedName fullDn = new DistinguishedName(userDn);
          fullDn.prepend(ctxSource.getBaseLdapPath());
  
  
          DirContext ctx = null;
          try {
              ctx = getContextSource().getContext(fullDn.toString(), password);
              // Check for password policy control
              PasswordPolicyControl ppolicy = PasswordPolicyControlExtractor.extractControl(ctx);
  
  
  
              Attributes attrs = ctx.getAttributes(userDn, getUserAttributes());
  
              DirContextAdapter result = new DirContextAdapter(attrs, userDn, ctxSource.getBaseLdapPath());
  
              if (ppolicy != null) {
                  result.setAttributeValue(ppolicy.getID(), ppolicy);
              }
  
              return result;
          } catch (NamingException e) {
              // This will be thrown if an invalid user name is used and the method may
              // be called multiple times to try different names, so we trap the exception
              // unless a subclass wishes to implement more specialized behaviour.
              if ((e instanceof org.springframework.ldap.AuthenticationException)
                      || (e instanceof org.springframework.ldap.OperationNotSupportedException)) {
                  handleBindException(userDnStr, username, e);
              } else {
                  throw e;
              }
          } catch (javax.naming.NamingException e) {
              throw LdapUtils.convertLdapException(e);
          } finally {
              LdapUtils.closeContext(ctx);
          }
  
          return null;
      }
  
      /**
       * Allows subclasses to inspect the exception thrown by an attempt to bind with a particular DN.
       * The default implementation just reports the failure to the debug logger.
       */
      protected void handleBindException(String userDn, String username, Throwable cause) {
         System.out.println("Failed to bind as " + userDn + ": " + cause);
      }
  </code></pre>
  
  <p>}</p>
  
  <p>If anyone has any experience with this kind of thing I would greatly appreciate this as I couldn't find much on this subject. I was hoping that someone could tell me if I am on the right track or if I should be going about this in a different way. Just to be clear I am using spring-security-ldap and not spring-ldap. Also just want to mention that I do have all my dependencies in my pom file. Thanks!</p>