More Active Directory integration in Java
I blogged earlier about the sorry state of Active Directory integration in Java and how I implemented zero-configuration Active Directory support in Hudson by talking to Microsoft COM API.
That was great for those of you who run Hudson on Windows, but since then, I discovered that many folks wanted to run Hudson on a Unix machine but still authenticate against Active Directory. So I needed to implement another approach that works on a Unix machine. It took a bit of research and experiments, but in the end I was able to make this work — now the Active Directory plugin for Hudson works also on Unix, and all it asks you to configure is just one thing, that is the domain name to authenticate with.
Since people still seem to be rather clueless and waste a lot of time by trying to configure general-purpose LDAP or JNDI authentication mechanism to talk to Active Directory, here's how your program can talk to Active Directory from domain name:
Step 1: Locate LDAP server
First, use DNS SRV record to find where the LDAP servers are. You can do this by querying SRV records for _ldap._tcp.DOMAINNAME. You can see more about this mechanism in MSDN. See this JavaSE document for more about how to query DNS through JNDI.
This allows users to avoid hard-coding LDAP server name, and so they won't need to update your config as domain controllers come and go. SRV records also return information about fallback servers and round-robin mechanism (like MX records), and your program can do the right thing.
Step 2: Authenticate
Once you figure out which port of which server the LDAP service is running, it's time to talk to LDAP, again through JNDI. In LDAP, the way you verify that the given password is correct is by trying to login as that user with the password. Unfortunately, many generic LDAP connector (like this one) does a rather round-about way of doing this — first they bind and locate the record of the user being authenticated, to figure out its "canonical name". Then they bind again, this time with the canonical name of the user plus the given password. If that binds successfully, then the password was correct. If not, the password was wrong. This is necessary because the "user name" to connect to LDAP is different from what you might think.
This is very bad idea with Active Directory, since it doesn't support anonymous bind by default. That is, the program needs to have at least one workable user/password pair to do the "figure out the canonical name" step. This forces you to write down a password in a configuration file. Very bad idea.
But you can do better with Active Directory LDAP service. With AD, you can skip everything and just try to connect with the user name and password that the user entered. The trick is to supply the fully qualified user name (like "firstname.lastname@example.org") as the user name, instead of a canonical name (like "CN=Kohsuke Kawaguchi,DC=sun,DC=com"). So by writing code specifically against AD, your user won't have to give you user name and password.
Once you bind to Active Directory through LDAP, you can query all the user/group membership information and more to your hearts content. See Active Directory schema documentation for what attributes are available. It's also useful to use some kind of LDAP browser to check your sanity and do trouble-shooting.
See the source code for more details. For those of you who are suffering because you need to configure Active Directory authentication through LDAP authenticator, please demand to your vendors that they write AD authenticator.
The next one up is to Integrated Windows Authentication. If we can do this nicely in Java (and I know we can — it's just a matter of a bit of additional effort), the Java webapps would work very nicely in Active Directory environment.