Mutant Mumblings

Tuesday Oct 09, 2007

Integrating Acegi with a Windows 2003 Small Business Server Active Directory Using LDAP

After using Acegi for the last year or so, i have needed to integrate a half dozen applications with Microsoft Active Directory. Doing this the first time was fairly painful because I couldn't find any good examples of what the configuration should really look like, just lots of small pieces. In hopes of saving a few people the many hours I spent working out the details, here is a solution

Assumptions:

  • This example is going to work with Win2K3 Small Business Server, the built in wizards actually put the users in a strange place that took a while to figure out, and I never found any good documentations for it.
  • My ADS domain is going to be called "SourceAllies.local"
  • I'm not going to use TLS for communications between the app server and ADS. In the real world you should always use TLS to protect your credentials, but it adds a whole set of configuration steps that I'm going to call outside the scope of this entry.

Step 1: Define the Initial Directory Context Factory

 We need to create a context factory that will provide Acegi with the location of our AD server, the username and password for the account we will use for connecting to the server and any extra information acegi might need to traverse the directory tree.

<bean id="initialDirContextFactory" 
class="org.acegisecurity.ldap.DefaultInitialDirContextFactory">
<constructor-arg value="ldap://SourceAllies.local:389/DC=SourceAllies,DC=local" /> (1)
<property name="managerDn" value="CN=acegi,CN=Users,DC=SourceAllies,DC=local" /> (2)
<property name="managerPassword" value="12345" /> (3)
<property name="extraEnvVars"> (4)
<map>
<entry key="java.naming.referral" value="follow" />
</map>
</property>
</bean> 
  1. The constructor-arg tells the context factory what the url to our ADS is including the base DN we want to use.  One of the nice things with ADS is that if everything in windows is set up correctly, the name of our ADS domain will also be the name of the ADS server and base DN we want to seach inside the LDAP tree.  So in the case "SourceAllies.local" is the server name, and our base DN is "DC=SourceAllies,DC=local".  Everything in the domain is stored under that DN.
  2. By default, ADS does not allow anonymous searches so we must provide a set of credentials.  Note that this user was NOT created using the small business server tools so it resides in the normal "CN=Users" section of the LDAP tree.
  3. This is the password for the "acegi" user in ADS
  4. ADS requires referrals be set to "follow" in order for queries to work properly.

Step 2: Define the User Search

Now we need to tell Acegi how it should search the LDAP tree to find our users. We do this by creating a search filter that will tell Acegi which directory context to look in, how to make the provided username to the LDAP tree and where in the context to start our search.

<bean id="userSearch" 
class="org.acegisecurity.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg index="0" value="" /> (1)
<constructor-arg index="1" value="(sAMAccountName={0})" /> (2)
<constructor-arg index="2" ref="initialDirContextFactory" /> (3)
<property name="searchSubtree" value="true" /> (4)
</bean>
  1.  We can provide a "serachBase" or offset from the initial directory context that we want to search, but in this case I want to look in the entire tree.
  2. This is the filter itself.  This will tell Acegi to find any entry with an sAMAccountName that equal our provided username.  sAMAccountName is the name of the LDAP attribute that ADS uses to store the windows account name.
  3. We provide the search filter with our directory context factory that we configured in a previous step.
  4. We want to search the entire tree, not just the top level, so we want the query to search the subtrees as well.

Step 3: Define the Authorities Populator

Once the user is authenticated, we need to find out what roles it has.  Acegi provides an authorites populator interface with an implementation that will let you query groups from the LDAP server.  You could also make your own impl that would look for roles in a database table or any other datastore.  But in this case we want to grab all their windows groups.

<bean id="authoritiesPopulator" 
class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
<constructor-arg ref="initialDirContextFactory" /> (1)
<constructor-arg value="OU=Security Groups,OU=MyBusiness" /> (2)
<property name="groupRoleAttribute" value="CN" /> (3)
<property name="rolePrefix" value="ROLE_" /> (4)
<property name="searchSubtree" value="true" />
</bean> 
  1. Provide our context factory defined in a previous Step
  2. We are going to search for groups in this part of the directory.  This is where Small Business Server's wizards will define groups.
  3. We are going to use the "CN" attribute of the group to define the roles name.
  4. To make this query match the defaults on the RoleVoter in our standard Acegi configuration, we will prefix  the name of all roles with "ROLE_" 

Step 4: Define the LDAP Authenticator

We need to tell Acegi how it should go about verifying the credentials our users provide.  Acegi provides several different implementations that can compare hashed values of passwords or compare other values,  but the option we are forced to choose is the BindAuthenticator.  This authenticator will try to attach to the ADS using the credentials provided, we have to use this method because ADS will not let external programs view even the hashed or encrypted values in the password fields.  However the bind authenticator is very easy to set up, it just needs to know how to find out directory context, and be able to find the correct user entry in the tree.

<bean id="ldapAuthenticator" 
class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
<constructor-arg ref="initialDirContextFactory" />
<property name="userSearch" ref="userSearch" />
</bean> 

 

Step 5:  Tie it all together and define the LDAP Provider

This is our primary LDAP authentication provider that will be integrated into the rest of the acegi setup.  This piece just needs to know how to authenticate a user and how to retrieve the roles for that user.

<bean id="ldapAuthProvider" 
class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider">
<constructor-arg ref="ldapAuthenticator" />
<constructor-arg ref="authoritiesPopulator" />
</bean> 

Final Step: Add the LDAP Provider to the Provider Manager

Most of the Acegi configuration doesn't care about where or how credentials are verified, it really only matters from the ProviderManager on down.

<bean id="providerManager" 
class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref bean="ldapAuthProvider" />
</list>
</property>
</bean>


Calendar

Feeds

Search

Links

Navigation