Categories
Coding

Acegi Security and NTLM

(UPDATE: there is a more up-to-date Spring Security example here: http://blog.mediasoft.be/?p=1).

In the tradition of the NTLM-related posts that I have previously written (see here, here, here, here and here), I have a final installment concerning how to get NTLM authentication to work with Acegi Security. Acegi has provisional support for NTLM authentication (currently the support is not bundled with the distribution, it’s in CVS only), and there is an open ticket here tracking the progress of that feature. Hopefully it will be bundled with 1.0, but for now, you can just download the CVS source tree and build it. The underlying NTLM support is built on top of JCIFS, which I have written about previously.

Once you have downloaded and built Acegi with NTLM support, you need to set up your application context. First, we add the NTLM filter and an entry point:

< bean id="ntlmEntryPoint" class="org.acegisecurity.ui.ntlm.NtlmProcessingFilterEntryPoint"/>

< bean id="ntlmFilter" class="org.acegisecurity.ui.ntlm.NtlmProcessingFilter">
<
property name="defaultDomain">< value>MYDOMAIN
<
property name="domainController" value="10.0.1.2"/>
<
property name="authenticationManager" ref="authenticationManager"/>
<
property name="authenticationEntryPoint" ref="ntlmEntryPoint"/>< /bean>

Next, we add a SecurityEnforcementFilter and pass it the entry point:

< bean id="securityEnforcementFilter" class="org.acegisecurity.intercept.web.SecurityEnforcementFilter">
<
property name="filterSecurityInterceptor">
<
ref bean="filterInvocationInterceptor"/>
<
property name="authenticationEntryPoint">
<
ref bean="ntlmEntryPoint"/>

Our filterSecurityInterceptor can be defined as follows:

<bean id="filterInvocationInterceptor"
class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="accessDecisionManager"><ref bean="accessDecisionManager"/></property>
<property name="objectDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=ROLE_ADMIN
</value>
</property>
</bean>

We referenced an authorisationManager in the above snippet, which we define as follows:


<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<bean id="smbAuthenticationProvider"
class="org.acegisecurity.providers.smb.SmbNtlmAuthenticationProvider">
<property name="authorizationProvider">
<ref local="daoAuthenticationProvider"/>
</property>
</bean>
</list>
</property>
</bean>

The SmbNtlmAuthenticationProvider above implements the authenticate() method, which will be used to verify the user’s credentials. Note that we have a reference to a daoAuthenticationProvider, which we define as follows:

<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.NullPasswordDaoAuthenticationProvider">

I will explain why we are using a custom provider later.

Now that we have defined a protected resource, we need to provide an authorisation store. For this simple example, I will use an InMemoryDaoImpl. Note that you probably wouldn’t do this in real life – I haven’t even bothered to specify a passwordEncoder here.

<bean id="memoryAuthenticationDao" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userMap">
<value>
jsmith=PASSWORD,ROLE_ADMIN
</value>
</property>
</bean>

We also need to specify an access decision mechanism. In this case we will just use the vanilla unanimous voting mechanism. Read the Acegi docs for more details on this procedure. For now, we just need to know that it’s there.


<bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter"/>


<bean id="accessDecisionManager" class="org.acegisecurity.vote.UnanimousBased">
<property name="allowIfAllAbstainDecisions">
<value>false</value>
</property>
<property name="decisionVoters">
<list>
<ref local="roleVoter"/>
</list>
</property>
</bean>

Now we can define the filter chain. In the application’s web.xml, we create the following entry:

<filter>
<filter-name>Acegi HTTP Request Security Filter</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>org.acegisecurity.util.FilterChainProxy</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>Acegi HTTP Request Security Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

We are going to use Spring’s “virtual filter chain” here. We thus define the order of the filters in Spring’s application context:


<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,ntlmFilter,securityEnforcementFilter
</value>
</property>
</bean>

The order of entries is important here. Our httpSessionContextIntegrationFilter is defined as:


<bean id="httpSessionContextIntegrationFilter"
class="org.acegisecurity.context.HttpSessionContextIntegrationFilter">
<property name="context" value="org.acegisecurity.context.SecurityContextImpl"/>
</bean>

Normally, this should be all there is to it. There are actually a couple of extra steps I needed to complete in order to get this to work, however:

  • Refactor NtlmAuthenticationToken to extend UsernamePasswordAuthenticationToken.
  • Implement the NullPasswordDaoAuthenticationProvider. I just copied the code for the existing DaoAuthenticationProvider and removed any logic in the additionalAuthenticationChecks() method. The reason for doing this is that by default, DaoAuthenticationProvider calls authentication.getCredentials(), which returns null in our case.

Some of these changes may be due to the manner in which I implemented the demo (i.e using an InMemoryDaoImpl itself, and not broader issues. There dies seem to be a lot of configuration required, however once it is written, it works a treat. After having worked on an inhouse security framework in the past, I have vowed to to look at Acegi first from now on whenever this requirement arises in the future.