# Authenticating against an Active Directory server, pt. 3

Posted on September 20, 2009 by Tommy McGuire
As I mentioned in my first post in this horrible, tedious series, the first step in authenticating against Active Directory is to locate an AD server. The two fundamental goals behind this step is to remove as much configuration as possible from the client side and to improve reliability by supporting multiple redundant servers. The first goal is successful; really, the only configuration needed from the client is the AD domain name. The second goal is also successful; the process will work for almost any number of servers, even if part of the set of servers is local and the rest are geographically scattered or unreachable due to network configuration. (There, I have written something complementary thing about a Microsoft product.)

In order to locate an AD server, the first step is to perform a DNS query to locate a group of potential servers; this is actually pretty trivial. I am using the dnsjava library; there are other alternatives floating around. Personally, I find the JNDI interface abhorrent (particularly the "set property, set property, set property, do it!" interface style and the whole "JNDI is independent of the underlying implementation" pseudo-feature). Anyway, dnsjava is where the org.xbill.DNS package comes from.

import java.util.ArrayList;import java.util.Collections;import java.util.Comparator;import java.util.List;import java.util.Random;import org.xbill.DNS.Lookup;import org.xbill.DNS.Record;import org.xbill.DNS.SRVRecord;import org.xbill.DNS.TextParseException;import org.xbill.DNS.Type;

To locate potential LDAP hosts for basic LDAP username/password authentication against AD, this code needs to look for SRV records matching _ldap._tcp.fully-qualified-domain-name; something along the lines of "_ldap._tcp.example.com", for Example.com's AD domain of EXAMPLE.COM, with an unqualified name of EXAMPLE. (I.e. a user would log into a workstation as EXAMPLE\jdoe.)

The argument to the following method is the service record lookup name, "_ldap._tcp.example.com".

  /**   * Return SRV records for the domain, sorted by priority and weight (ahem).   *    * This uses the SRVRecordComparator class below; see it for the caveat about the order.   *    * @param domain String domain name to search for records.   * @return List of matching SRV records, sorted by priority and weight.   * @throws SecuritySystemException If the domain is ill-formed.   */  public List getSrvRecords(String domain) throws SecuritySystemException  {    try    {      Record[] records = new Lookup(domain, Type.SRV).run();      List srvRecords = new ArrayList(records.length);      for (Record record : records)      {        srvRecords.add((SRVRecord) record);      }      Collections.sort(srvRecords, new SRVRecordComparator());      return srvRecords;    }    catch (TextParseException e)    {      String msg = "domain <" + domain + "> is not a valid DNS name";      log.error(msg, e);      throw new SecuritySystemException(msg, e);    }    }

(That method needs some extra error condition checking, by the way.)

SRV records contain a priority value, which is used to order the records in the list, and a weighting, which is used to order records within a priority for load balancing purposes. (For example, if each of the n records within a priority have a weighting of wk, for k between 0 and n - 1, then the first record would be chosen randomly, with a probability of wk/w where w is the sum of the wk's and so on.)

However, in my environment, all of the SRV records have the same priority and weight. Further, the choice of with host to use will be made later, by the LDAP ping process. Therefore, I am simply sorting the records by priority and randomly within priorities.

  /**   * Comparator to sort SRV Records by priority and to shuffle the SRV records within priority.   * The idea here is to sort the list of records by priority, then shuffle the records within   * each priority by comparing priorities and then flipping a coin when the priorities are equal.     * This is technically incorrect, since the choice within priority should be   * random by weighting, but I'm lazy.  (And the SRV records I have at the moment have all the same   * priorities and weights).   *    * According to apfelmus, http://apfelmus.nfshost.com/random-permutations.html,   *    * "Yes, there is! The main idea is that shuffling a list is essentially the same as sorting a    * list; the minor difference being that the former chooses a permutation at random while the    * latter chooses a very particular permutation, namely the one that sorts the input. In    * other words, the idea is to take a generic sorting algorithm like merge sort and turn it    * into a shuffle algorithm by simply replacing each comparison with a random choice."   *    * @author mcguire   *   */  private static final class SRVRecordComparator implements Comparator  {    private static final Random generator = new Random();        @Override    public int compare(SRVRecord left, SRVRecord right)    {      int relativePriority = Integer.signum(right.getPriority() - left.getPriority());      if (relativePriority == 0)      {        return (generator.nextBoolean() ? -1 : 1);      }      else      {        return relativePriority;      }    }  }

The result of this code is a list of SRV records, sorted somehow. The next part of the series will attempt to locate a the best server from the list via LDAP pings.

Ideally, you'll:
1. Try to contact a global catalog (to resolve alternate UPNs)
2. Try to contact a domain controller in your site

or combining the two: