浏览代码

SEC-8: Imported contributed NTLM code. Some changes because of current state of LDAP code.

Luke Taylor 18 年之前
父节点
当前提交
8762ffabbe

+ 5 - 0
ntlm/README

@@ -0,0 +1,5 @@
+Just place this folder into the SVN checkout of ACEGI sources.
+Then modify the root pom.xml to include the folder as a module.
+
+The applicationContext.xml and web.xml files are included in
+the root directory for example purposes only.

+ 95 - 0
ntlm/applicationContext.xml

@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<beans>
+
+	<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
+		<property name="filterInvocationDefinitionSource">
+			<value>
+			CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
+			PATTERN_TYPE_APACHE_ANT
+			/login_error.jsp=httpSessionContextIntegrationFilter
+			/**=httpSessionContextIntegrationFilter, exceptionTranslationFilter, ntlmFilter, filterSecurityInterceptor 
+			</value>
+		</property>
+	</bean>
+
+	<!-- The first item in the Chain: httpSessionContextIntegrationFilter -->
+	<bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter">
+		<property name="context">
+			<value>org.acegisecurity.context.SecurityContextImpl</value>
+		</property>
+	</bean>
+
+	<!-- the second item in the chain: exceptionTranslationFilter -->
+	<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
+		<property name="authenticationEntryPoint" ref="ntlmEntryPoint"/>
+	</bean>
+
+	<!-- the third item in the chain: ntlmFilter -->
+	<bean id="ntlmFilter" class="org.acegisecurity.ui.ntlm.NtlmProcessingFilter">
+		<property name="defaultDomain" value="YOURDOMAIN"/>
+		<!-- It is better to use a WINS server if available over a specific domain controller
+ 		<property name="domainController" value="FOO"/> -->
+		<property name="netbiosWINS" value="192.168.0.3"/>
+		<property name="authenticationManager" ref="providerManager"/>
+	</bean>
+
+	<bean id="providerManager" class="org.acegisecurity.providers.ProviderManager">
+		<property name="providers">
+			<list>
+				<ref local="daoAuthenticationProvider"/>
+            </list>
+        </property>
+    </bean>
+
+	<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
+		<property name="userDetailsService">
+			<ref local="memoryUserDetailsService"/>
+		</property>
+	</bean>
+
+	<!-- NOTE: You will need to write a custom UserDetailsService in most cases -->
+	<bean id="memoryUserDetailsService" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
+		<property name="userMap">
+			<value>jdoe=PASSWORD,ROLE_USER</value>
+		</property>
+	</bean>
+
+	<!-- the fourth item in the chain: filterSecurityInterceptor -->
+	<bean id="filterSecurityInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
+		<property name="authenticationManager"><ref local="providerManager"/></property>
+		<property name="accessDecisionManager"><ref local="accessDecisionManager"/></property>
+		<property name="objectDefinitionSource">
+			<value>
+			CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
+			PATTERN_TYPE_APACHE_ANT
+			/**=ROLE_USER
+			</value>
+		</property>
+	</bean>
+
+	<!-- authenticationManager defined above -->
+	<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>
+
+	<bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter"/>
+
+	<bean id="ntlmEntryPoint" class="org.acegisecurity.ui.ntlm.NtlmProcessingFilterEntryPoint">
+		<property name="authenticationFailureUrl" value="/login_error.jsp"/>
+	</bean>
+
+	<!-- Done with the chain -->
+
+	<!-- This bean automatically receives AuthenticationEvent messages from DaoAuthenticationProvider -->
+	<bean id="loggerListener" class="org.acegisecurity.event.authentication.LoggerListener"/>
+
+</beans>

+ 65 - 0
ntlm/pom.xml

@@ -0,0 +1,65 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.acegisecurity</groupId>
+		<artifactId>acegi-security-parent</artifactId>
+		<version>2.0-SNAPSHOT</version>
+	</parent>
+	<packaging>jar</packaging>
+	<artifactId>spring-security-ntlm</artifactId>
+	<name>Spring Security - NTLM</name>
+
+	<dependencies>
+		<dependency>
+	      <groupId>org.acegisecurity</groupId>
+	      <artifactId>acegi-security</artifactId>
+	      <version>${project.version}</version>
+		</dependency>
+		<!-- SMT NTLM-->
+		<dependency>
+		    <groupId>org.samba.jcifs</groupId>
+		    <artifactId>jcifs</artifactId>
+		    <version>1.2.15</version>
+		</dependency>
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>jsp-api</artifactId>
+			<version>2.0</version>
+			<optional>true</optional>
+		</dependency>
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>servlet-api</artifactId>
+			<version>2.4</version>
+			<optional>true</optional>
+		</dependency>
+        <dependency>
+            <groupId>org.springframework.ldap</groupId>
+            <artifactId>spring-ldap</artifactId>
+            <version>1.2-RC1</version>
+            <optional>true</optional>
+        </dependency>        
+    </dependencies>
+
+	<build>
+		<resources>
+			<resource>
+				<directory>${basedir}/../</directory>
+				<targetPath>META-INF</targetPath>
+				<includes>
+					<include>notice.txt</include>
+				</includes>
+				<filtering>false</filtering>
+			</resource>
+			<resource>
+				<directory>${basedir}/src/main/resources</directory>
+				<targetPath>/</targetPath>
+				<includes>
+					<include>**/*</include>
+				</includes>
+				<filtering>false</filtering>
+			</resource>
+		</resources>
+	</build>
+
+</project>

+ 61 - 0
ntlm/src/main/java/org/acegisecurity/ui/ntlm/HttpFilter.java

@@ -0,0 +1,61 @@
+/* Copyright 2004-2007 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.ui.ntlm;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public abstract class HttpFilter implements Filter {
+
+	public void init(FilterConfig config) throws ServletException {}
+
+	public void destroy() {}
+
+	public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
+		if (!(request instanceof HttpServletRequest)) {
+			throw new ServletException("Can only process HttpServletRequest");
+		}
+
+		if (!(response instanceof HttpServletResponse)) {
+			throw new ServletException("Can only process HttpServletResponse");
+		}
+
+		this.doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
+
+		chain.doFilter(request, response);
+	}
+
+//	*************************** Protected Methods ****************************
+
+	protected abstract void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException;
+
+	protected void sendRedirect(final HttpServletRequest request, final HttpServletResponse response, String url) throws IOException {
+		if (!url.startsWith("http://") && !url.startsWith("https://")) {
+			url = request.getContextPath() + url;
+		}
+
+		response.sendRedirect(response.encodeRedirectURL(url));
+	}
+
+}	// End HttpFilter

+ 34 - 0
ntlm/src/main/java/org/acegisecurity/ui/ntlm/NtlmBaseException.java

@@ -0,0 +1,34 @@
+/* Copyright 2004-2007 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.ui.ntlm;
+
+import org.acegisecurity.AuthenticationException;
+
+/**
+ * Base class for NTLM exceptions so that it is easier to distinguish them
+ * from other <code>AuthenticationException</code>s in the
+ * {@link NtlmProcessingFilterEntryPoint}.  Marked as <code>abstract</code>
+ * since this exception is never supposed to be instantiated.
+ *
+ * @author Edward Smith
+ */
+public abstract class NtlmBaseException extends AuthenticationException {
+
+	public NtlmBaseException(final String msg) {
+		super(msg);
+	}
+
+}	// End NtlmBaseException

+ 31 - 0
ntlm/src/main/java/org/acegisecurity/ui/ntlm/NtlmBeginHandshakeException.java

@@ -0,0 +1,31 @@
+/* Copyright 2004-2007 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.ui.ntlm;
+
+import org.acegisecurity.AuthenticationException;
+
+/**
+ * Signals the beginning of an NTLM handshaking process.
+ *
+ * @author Edward Smith
+ */
+public class NtlmBeginHandshakeException extends NtlmBaseException {
+
+	public NtlmBeginHandshakeException() {
+		super("NTLM");
+	}
+
+}	// End NtlmBeginHandshakeException

+ 511 - 0
ntlm/src/main/java/org/acegisecurity/ui/ntlm/NtlmProcessingFilter.java

@@ -0,0 +1,511 @@
+/* Copyright 2004-2007 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.ui.ntlm;
+
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+import java.util.Properties;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import jcifs.Config;
+import jcifs.UniAddress;
+import jcifs.ntlmssp.Type1Message;
+import jcifs.ntlmssp.Type2Message;
+import jcifs.ntlmssp.Type3Message;
+import jcifs.smb.NtlmChallenge;
+import jcifs.smb.NtlmPasswordAuthentication;
+import jcifs.smb.SmbAuthException;
+import jcifs.smb.SmbException;
+import jcifs.smb.SmbSession;
+import jcifs.util.Base64;
+
+import org.acegisecurity.AcegiMessageSource;
+import org.acegisecurity.Authentication;
+import org.acegisecurity.AuthenticationCredentialsNotFoundException;
+import org.acegisecurity.AuthenticationException;
+import org.acegisecurity.AuthenticationManager;
+import org.acegisecurity.BadCredentialsException;
+import org.acegisecurity.InsufficientAuthenticationException;
+import org.acegisecurity.context.SecurityContextHolder;
+import org.acegisecurity.event.authentication.InteractiveAuthenticationSuccessEvent;
+import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
+import org.acegisecurity.ui.AbstractProcessingFilter;
+import org.acegisecurity.ui.WebAuthenticationDetails;
+import org.acegisecurity.ui.savedrequest.SavedRequest;
+import org.acegisecurity.ui.webapp.AuthenticationProcessingFilter;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.support.MessageSourceAccessor;
+import org.springframework.util.Assert;
+
+/**
+ * A clean-room implementation for Acegi Security System of an NTLM HTTP filter
+ * leveraging the JCIFS library.
+ * <p>
+ * NTLM is a Microsoft-developed protocol providing single sign-on capabilities
+ * to web applications and other integrated applications.  It allows a web
+ * server to automatcially discover the username of a browser client when that
+ * client is logged into a Windows domain and is using an NTLM-aware browser.
+ * A web application can then reuse the user's Windows credentials without
+ * having to ask for them again.   
+ * <p>
+ * Because NTLM only provides the username of the Windows client, an Acegi
+ * Security NTLM deployment must have a <code>UserDetailsService</code> that
+ * provides a <code>UserDetails</code> object with the empty string as the
+ * password and whatever <code>GrantedAuthority</code> values necessary to
+ * pass the <code>FilterSecurityInterceptor</code>.
+ * <p>
+ * The Acegi Security bean configuration file must also place the
+ * <code>ExceptionTranslationFilter</code> before this filter in the
+ * <code>FilterChainProxy</code> definition.
+ * 
+ * @author Davide Baroncelli
+ * @author Edward Smith
+ * @version $Id$
+ */
+public class NtlmProcessingFilter extends HttpFilter implements InitializingBean {
+	//~ Static fields/initializers =====================================================================================
+
+	private static Log	logger = LogFactory.getLog(NtlmProcessingFilter.class);
+
+	private static final String	STATE_ATTR = "AcegiNtlm";
+	private static final String	CHALLENGE_ATTR = "NtlmChal";
+	private static final Integer BEGIN = Integer.valueOf(0);
+	private static final Integer NEGOTIATE = Integer.valueOf(1);
+	private static final Integer COMPLETE = Integer.valueOf(2);
+	private static final Integer DELAYED = Integer.valueOf(3);
+
+	//~ Instance fields ================================================================================================
+
+	/** Shoud the filter load balance among multiple domain controllers, default <code>false</code>		*/
+	private boolean	loadBalance;
+
+	/** Shoud the domain name be stripped from the username, default <code>true</code>					*/
+	private boolean stripDomain = true;
+
+	/** Should the filter initiate NTLM negotiations, default <code>true</code>							*/
+	private boolean forceIdentification = true;
+
+	/** Shoud the filter retry NTLM on authorization failure, default <code>false</code>				*/
+	private boolean retryOnAuthFailure;
+
+	private String	soTimeout;
+	private String	cachePolicy;
+	private String	defaultDomain;
+	private String	domainController;
+	private AuthenticationManager authenticationManager;
+
+	//~ Public Methods =================================================================================================
+
+	/**
+	 * Ensures an <code>AuthenticationManager</code> and authentication failure
+	 * URL have been provided in the bean configuration file.
+	 */
+	public void afterPropertiesSet() throws Exception {
+		Assert.notNull(this.authenticationManager, "An AuthenticationManager is required");
+
+		// Default to 5 minutes if not already specified
+		Config.setProperty("jcifs.smb.client.soTimeout", (soTimeout == null) ? "300000" : soTimeout);
+		// Default to 20 minutes if not already specified
+		Config.setProperty("jcifs.netbios.cachePolicy", (cachePolicy == null) ? "1200" : cachePolicy);
+
+		if (domainController == null) {
+			domainController = defaultDomain;
+		}
+	}
+
+	/**
+	 * Sets the <code>AuthenticationManager</code> to use.
+	 * 
+	 * @param authenticationManager the <code>AuthenticationManager</code> to use.
+	 */
+	public void setAuthenticationManager(AuthenticationManager authenticationManager) {
+		this.authenticationManager = authenticationManager;
+	}
+
+	/**
+	 * The NT domain against which clients should be authenticated. If the SMB
+	 * client username and password are also set, then preauthentication will
+	 * be used which is necessary to initialize the SMB signing digest. SMB
+	 * signatures are required by default on Windows 2003 domain controllers.
+	 * 
+	 * @param defaultDomain The name of the default domain.
+	 */
+	public void setDefaultDomain(String defaultDomain) {
+		this.defaultDomain = defaultDomain;
+		Config.setProperty("jcifs.smb.client.domain", defaultDomain);
+	}
+
+	/**
+	 * Sets the SMB client username.
+	 * 
+	 * @param smbClientUsername The SMB client username.
+	 */
+	public void setSmbClientUsername(String smbClientUsername) {
+		Config.setProperty("jcifs.smb.client.username", smbClientUsername);
+	}
+
+	/**
+	 * Sets the SMB client password.
+	 * 
+	 * @param smbClientPassword The SMB client password.
+	 */
+	public void setSmbClientPassword(String smbClientPassword) {
+		Config.setProperty("jcifs.smb.client.password", smbClientPassword);
+	}
+
+	/**
+	 * Sets the SMB client SSN limit. When set to <code>1</code>, every
+	 * authentication is forced to use a separate transport. This effectively
+	 * ignores SMB signing requirements, however at the expense of reducing
+	 * scalability. Preauthentication with a domain, username, and password is
+	 * the preferred method for working with servers that require signatures. 
+	 * 
+	 * @param smbClientSSNLimit The SMB client SSN limit.
+	 */
+	public void setSmbClientSSNLimit(String smbClientSSNLimit) {
+		Config.setProperty("jcifs.smb.client.ssnLimit", smbClientSSNLimit);
+	}
+
+	/**
+	 * Configures JCIFS to use a WINS server.  It is preferred to use a WINS
+	 * server over a specific domain controller.  Set this property instead of
+	 * <code>domainController</code> if there is a WINS server available.
+	 * 
+	 * @param netbiosWINS The WINS server JCIFS will use.
+	 */
+	public void setNetbiosWINS(String netbiosWINS) {
+		Config.setProperty("jcifs.netbios.wins", netbiosWINS);  
+	}
+
+	/**
+	 * The IP address of any SMB server that should be used to authenticate
+	 * HTTP clients.
+	 * 
+	 * @param domainController The IP address of the domain controller.
+	 */
+	public void setDomainController(String domainController) {
+		this.domainController = domainController;
+	}
+
+	/**
+	 * If the default domain is specified and the domain controller is not
+	 * specified, then query for domain controllers by name.  When load
+	 * balance is <code>true</code>, rotate through the list of domain
+	 * controllers when authenticating users.
+	 * 
+	 * @param loadBalance The load balance flag value.
+	 */
+	public void setLoadBalance(boolean loadBalance) {
+		this.loadBalance = loadBalance;
+	}
+
+	/**
+	 * Configures <code>NtlmProcessingFilter</code> to strip the Windows
+	 * domain name from the username when set to <code>true</code>, which
+	 * is the default value.
+	 *  
+	 * @param stripDomain The strip domain flag value.
+	 */
+	public void setStripDomain(boolean stripDomain) {
+		this.stripDomain = stripDomain;
+	}
+
+	/**
+	 * Sets the <code>jcifs.smb.client.soTimeout</code> property to the
+	 * timeout value specified in milliseconds. Defaults to 5 minutes
+	 * if not specified.
+	 * 
+	 * @param timeout The milliseconds timeout value.
+	 */
+	public void setSoTimeout(String timeout) {
+		this.soTimeout = timeout;
+	}
+
+	/**
+	 * Sets the <code>jcifs.netbios.cachePolicy</code> property to the
+	 * number of seconds a NetBIOS address is cached by JCIFS. Defaults to
+	 * 20 minutes if not specified.
+	 * 
+	 * @param numSeconds The number of seconds a NetBIOS address is cached.
+	 */
+	public void setCachePolicy(String numSeconds) {
+		this.cachePolicy = numSeconds;
+	}
+
+	/**
+	 * Loads properties starting with "jcifs" into the JCIFS configuration.
+	 * Any other properties are ignored.
+	 * 
+	 * @param props The JCIFS properties to set.
+	 */
+	public void setJcifsProperties(Properties props) {
+		String name;
+
+		for (Enumeration e=props.keys(); e.hasMoreElements();) {
+			name = (String) e.nextElement();
+			if (name.startsWith("jcifs.")) {
+				Config.setProperty(name, props.getProperty(name));
+			}
+        }
+	}
+
+	/**
+	 * Returns <code>true</code> if NTLM authentication is forced.
+	 * 
+	 * @return <code>true</code> if NTLM authentication is forced.
+	 */
+	public boolean isForceIdentification() {
+		return this.forceIdentification;
+	}
+
+	/**
+	 * Sets a flag denoting whether NTLM authentication should be forced.
+	 * 
+	 * @param forceIdentification the force identification flag value to set.
+	 */
+	public void setForceIdentification(boolean forceIdentification) {
+		this.forceIdentification = forceIdentification;
+	}
+
+	/**
+	 * Sets a flag denoting whether NTLM should retry whenever authentication
+	 * fails.  Retry will only occur on an {@link AuthenticationCredentialsNotFoundException}
+	 * or {@link InsufficientAuthenticationException}.
+	 * 
+	 * @param retryOnFailure the retry on failure flag value to set.
+	 */
+	public void setRetryOnAuthFailure(boolean retryOnFailure) {
+		this.retryOnAuthFailure = retryOnFailure;
+	}
+
+	//~ Protected Methods ==============================================================================================
+
+	protected void doFilter(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws IOException {
+		final HttpSession session = request.getSession();
+		Integer ntlmState = (Integer) session.getAttribute(STATE_ATTR);
+
+		// Start NTLM negotiations the first time through the filter
+		if (ntlmState == null) {
+			if (forceIdentification) {
+				logger.debug("Starting NTLM handshake");
+				session.setAttribute(STATE_ATTR, BEGIN);
+				throw new NtlmBeginHandshakeException();
+			} else {
+				logger.debug("NTLM handshake not yet started");
+				session.setAttribute(STATE_ATTR, DELAYED);
+			}
+		}
+
+		// IE will send a Type 1 message to reauthenticate the user during an HTTP POST
+		if (ntlmState == COMPLETE && this.reAuthOnIEPost(request))
+			ntlmState = BEGIN;
+
+		final String authMessage = request.getHeader("Authorization");
+		if (ntlmState != COMPLETE && authMessage != null && authMessage.startsWith("NTLM ")) {
+			final UniAddress dcAddress = this.getDCAddress(session);
+			if (ntlmState == BEGIN) {
+				logger.debug("Processing NTLM Type 1 Message");
+				session.setAttribute(STATE_ATTR, NEGOTIATE);
+				this.processType1Message(authMessage, session, dcAddress);
+			} else {
+				logger.debug("Processing NTLM Type 3 Message");
+				final NtlmPasswordAuthentication auth = this.processType3Message(authMessage, session, dcAddress);
+				logger.debug("NTLM negotiation complete");
+				this.logon(session, dcAddress, auth);
+				session.setAttribute(STATE_ATTR, COMPLETE);
+
+				// Do not reauthenticate the user in Acegi during an IE POST
+				final Authentication myCurrentAuth = SecurityContextHolder.getContext().getAuthentication();
+				if (myCurrentAuth == null || myCurrentAuth instanceof AnonymousAuthenticationToken) {
+					logger.debug("Authenticating user credentials");
+					this.authenticate(request, response, session, auth);
+				}
+			}
+		}
+	}
+
+	//~ Private Methods ================================================================================================
+
+	/**
+	 * Returns <code>true</code> if reauthentication is needed on an IE POST.
+	 */
+	private boolean reAuthOnIEPost(final HttpServletRequest request) {
+		String ua = request.getHeader("User-Agent");
+		return (request.getMethod().equalsIgnoreCase("POST") && ua != null && ua.indexOf("MSIE") != -1);
+	}
+
+	/**
+	 * Creates and returns a Type 2 message from the provided Type 1 message.
+	 *  
+	 * @param message the Type 1 message to process.
+	 * @param session the <code>HTTPSession</code> object.
+	 * @param dcAddress the domain controller address.
+	 * @throws IOException
+	 */
+	private void processType1Message(final String message, final HttpSession session, final UniAddress dcAddress) throws IOException {
+		final Type2Message type2msg = new Type2Message(
+				new Type1Message(Base64.decode(message.substring(5))),
+				this.getChallenge(session, dcAddress),
+				null);
+		throw new NtlmType2MessageException(Base64.encode(type2msg.toByteArray()));
+	}
+
+	/**
+	 * Builds and returns an <code>NtlmPasswordAuthentication</code> object
+	 * from the provided Type 3 message.
+	 * 
+	 * @param message the Type 3 message to process.
+	 * @param session the <code>HTTPSession</code> object.
+	 * @param dcAddress the domain controller address.
+	 * @return an <code>NtlmPasswordAuthentication</code> object.
+	 * @throws IOException
+	 */
+	private NtlmPasswordAuthentication processType3Message(final String message, final HttpSession session, final UniAddress dcAddress) throws IOException {
+		final Type3Message type3msg = new Type3Message(Base64.decode(message.substring(5)));
+		final byte[] lmResponse = (type3msg.getLMResponse() != null) ? type3msg.getLMResponse() : new byte[0];
+		final byte[] ntResponse = (type3msg.getNTResponse() != null) ? type3msg.getNTResponse() : new byte[0];
+		return new NtlmPasswordAuthentication(
+				type3msg.getDomain(),
+				type3msg.getUser(),
+				this.getChallenge(session, dcAddress),
+				lmResponse,
+				ntResponse);
+	}
+
+	/**
+	 * Checks the user credentials against the domain controller.
+	 * 
+	 * @param session the <code>HTTPSession</code> object.
+	 * @param dcAddress the domain controller address.
+	 * @param auth the <code>NtlmPasswordAuthentication</code> object.
+	 * @throws IOException
+	 */
+	private void logon(final HttpSession session, final UniAddress dcAddress, final NtlmPasswordAuthentication auth) throws IOException {
+		try {
+			SmbSession.logon(dcAddress, auth);
+			if (logger.isDebugEnabled()) {
+				logger.debug(auth + " successfully authenticated against " + dcAddress);
+			}
+		} catch(SmbAuthException e) {
+			logger.error("Credentials " + auth + " were not accepted by the domain controller " + dcAddress);
+			throw new BadCredentialsException("Bad NTLM credentials");
+		} finally {
+			if (loadBalance)
+				session.removeAttribute(CHALLENGE_ATTR);
+		}
+	}
+
+	/**
+	 * Authenticates the user credentials acquired from NTLM against the Acegi
+	 * Security <code>AuthenticationManager</code>.
+	 * 
+	 * @param request the <code>HttpServletRequest</code> object.
+	 * @param response the <code>HttpServletResponse</code> object.
+	 * @param session the <code>HttpSession</code> object.
+	 * @param auth the <code>NtlmPasswordAuthentication</code> object.
+	 * @throws IOException
+	 */
+	private void authenticate(final HttpServletRequest request, final HttpServletResponse response, final HttpSession session, final NtlmPasswordAuthentication auth) throws IOException {
+		final Authentication authResult;
+		final UsernamePasswordAuthenticationToken authRequest;
+		final Authentication backupAuth;
+
+		authRequest = new NtlmUsernamePasswordAuthenticationToken(auth, stripDomain);
+		authRequest.setDetails(new WebAuthenticationDetails(request));
+
+		// Place the last username attempted into HttpSession for views
+		session.setAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_LAST_USERNAME_KEY, authRequest.getName());
+
+		// Backup the current authentication in case of an AuthenticationException
+		backupAuth = SecurityContextHolder.getContext().getAuthentication();
+
+		try {
+			// Authenitcate the user with the authentication manager
+			authResult = authenticationManager.authenticate(authRequest);
+		} catch (AuthenticationException failed) {
+			if (logger.isInfoEnabled()) {
+				logger.info("Authentication request for user: " + authRequest.getName() + " failed: " + failed.toString());
+			}
+
+			// Reset the backup Authentication object and rethrow the AuthenticationException
+			SecurityContextHolder.getContext().setAuthentication(backupAuth);
+
+			if (retryOnAuthFailure && (failed instanceof AuthenticationCredentialsNotFoundException || failed instanceof InsufficientAuthenticationException)) {
+				logger.debug("Restart NTLM authentication handshake due to AuthenticationException");
+				session.setAttribute(STATE_ATTR, BEGIN);
+				throw new NtlmBeginHandshakeException();
+			}
+
+			throw failed;
+		}
+
+		// Set the Authentication object with the valid authentication result
+		SecurityContextHolder.getContext().setAuthentication(authResult);
+	}
+
+	/**
+	 * Returns the domain controller address based on the <code>loadBalance</code>
+	 * setting.
+	 * 
+	 * @param session the <code>HttpSession</code> object.
+	 * @return the domain controller address.
+	 * @throws UnknownHostException
+	 * @throws SmbException
+	 */
+	private UniAddress getDCAddress(final HttpSession session) throws UnknownHostException, SmbException {
+		if (loadBalance) {
+			NtlmChallenge chal = (NtlmChallenge) session.getAttribute(CHALLENGE_ATTR);
+			if (chal == null) {
+				chal = SmbSession.getChallengeForDomain();
+				session.setAttribute(CHALLENGE_ATTR, chal);
+			}
+			return chal.dc;
+		}
+
+		return UniAddress.getByName(domainController, true);
+	}
+
+	/**
+	 * Returns the domain controller challenge based on the <code>loadBalance</code>
+	 * setting.
+	 * 
+	 * @param session the <code>HttpSession</code> object.
+	 * @param dcAddress the domain controller address.
+	 * @return the domain controller challenge.
+	 * @throws UnknownHostException
+	 * @throws SmbException
+	 */
+	private byte[] getChallenge(final HttpSession session, final UniAddress dcAddress) throws UnknownHostException, SmbException {
+		if (loadBalance)
+			return ((NtlmChallenge) session.getAttribute(CHALLENGE_ATTR)).challenge;
+
+		return SmbSession.getChallenge(dcAddress);
+	}
+
+}	// End NtlmProcessingFilter

+ 119 - 0
ntlm/src/main/java/org/acegisecurity/ui/ntlm/NtlmProcessingFilterEntryPoint.java

@@ -0,0 +1,119 @@
+/* Copyright 2004-2007 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.ui.ntlm;
+
+import org.acegisecurity.AuthenticationCredentialsNotFoundException;
+import org.acegisecurity.AuthenticationException;
+import org.acegisecurity.InsufficientAuthenticationException;
+import org.acegisecurity.ui.AuthenticationEntryPoint;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.util.Assert;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import jcifs.Config;
+
+/**
+ * Used by <code>ExceptionTranslationFilter</code> to assist with the NTLM
+ * negotiation.  Also handles redirecting the user to the authentication
+ * failure URL if an {@link AuthenticationException} that is not a subclass of
+ * {@link NtlmBaseException} is received.
+ *
+ * @author Davide Baroncelli
+ * @author Edward Smith
+ * @version $Id$
+ */
+public class NtlmProcessingFilterEntryPoint implements AuthenticationEntryPoint, InitializingBean {
+    //~ Static fields/initializers =============================================
+
+    private static final Log logger = LogFactory.getLog(NtlmProcessingFilterEntryPoint.class);
+
+	//~ Instance fields ================================================================================================
+
+	/** Where to redirect the browser to if authentication fails		*/
+	private String authenticationFailureUrl;
+
+	//~ Methods ================================================================
+
+	/**
+	 * Ensures an authentication failure URL has been provided in the bean
+	 * configuration file.
+	 */
+	public void afterPropertiesSet() throws Exception {
+		Assert.hasLength(authenticationFailureUrl, "authenticationFailureUrl must be specified");
+	}
+
+	/**
+	 * Sets the authentication failure URL.
+	 * 
+	 * @param authenticationFailureUrl the authentication failure URL.
+	 */
+	public void setAuthenticationFailureUrl(String authenticationFailureUrl) {
+		this.authenticationFailureUrl = authenticationFailureUrl;
+	}
+
+	/**
+	 * Sends an NTLM challenge to the browser requiring authentication. The
+	 * WWW-Authenticate header is populated with the appropriate information
+	 * during the negotiation lifecycle by calling the getMessage() method
+	 * from an NTLM-specific subclass of {@link NtlmBaseException}:
+	 * <p>
+	 * <ul>
+	 * <li>{@link NtlmBeginHandshakeException}: NTLM
+	 * <li>{@link NtlmType2MessageException}: NTLM &lt;base64-encoded type-2-message&gt;
+	 * </ul>
+	 * 
+	 * If the {@link AuthenticationException} is not a subclass of
+	 * {@link NtlmBaseException}, then redirect the user to the authentication
+	 * failure URL.
+	 * 
+	 * @param request The {@link HttpServletRequest} object.
+	 * @param response Then {@link HttpServletResponse} object.
+	 * @param authException Either {@link NtlmBeginHandshakeException},
+	 * 						{@link NtlmType2MessageException}, or
+	 * 						{@link AuthenticationException}
+	 */
+	public void commence(final ServletRequest request, final ServletResponse response, final AuthenticationException authException) throws IOException, ServletException {
+		final HttpServletResponse resp = (HttpServletResponse) response;
+
+		if (authException instanceof NtlmBaseException) {
+			if (authException instanceof NtlmType2MessageException) {
+				((NtlmType2MessageException) authException).preserveAuthentication();
+			}
+			resp.setHeader("WWW-Authenticate", authException.getMessage());
+			resp.setHeader("Connection", "Keep-Alive");
+			resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+			resp.setContentLength(0);
+			resp.flushBuffer();
+		} else {
+			String url = authenticationFailureUrl;
+			if (!url.startsWith("http://") && !url.startsWith("https://")) {
+				url = ((HttpServletRequest) request).getContextPath() + url;
+			}
+
+			resp.sendRedirect(resp.encodeRedirectURL(url));
+		}
+	}
+
+}	// End NtlmProcessingFilterEntryPoint

+ 48 - 0
ntlm/src/main/java/org/acegisecurity/ui/ntlm/NtlmType2MessageException.java

@@ -0,0 +1,48 @@
+/* Copyright 2004-2007 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.ui.ntlm;
+
+import org.acegisecurity.Authentication;
+import org.acegisecurity.AuthenticationException;
+import org.acegisecurity.context.SecurityContextHolder;
+
+/**
+ * Contains the NTLM Type 2 message that is sent back to the client during
+ * negotiations.
+ *
+ * @author Edward Smith
+ */
+public class NtlmType2MessageException extends NtlmBaseException {
+
+	private static final long serialVersionUID = 1L;
+
+	private final Authentication auth;
+
+	public NtlmType2MessageException(final String type2Msg) {
+		super("NTLM " + type2Msg);
+		auth = SecurityContextHolder.getContext().getAuthentication();
+	}
+
+	/**
+	 * Preserve the existing <code>Authentication</code> object each time
+	 * Internet Explorer does a POST.
+	 */
+	public void preserveAuthentication() {
+		if (auth != null)
+			SecurityContextHolder.getContext().setAuthentication(auth);
+	}
+
+}	// End NTLMType2MessageException

+ 49 - 0
ntlm/src/main/java/org/acegisecurity/ui/ntlm/NtlmUsernamePasswordAuthenticationToken.java

@@ -0,0 +1,49 @@
+/* Copyright 2004-2007 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.ui.ntlm;
+
+import jcifs.smb.NtlmPasswordAuthentication;
+
+import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+
+/**
+ * An NTLM-specific {@link UsernamePasswordAuthenticationToken} that allows
+ * any provider to bypass the problem of an empty password since NTLM does
+ * not retrieve the user's password from the PDC.
+ * 
+ * @author Sylvain Mougenot
+ */
+public class NtlmUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
+
+	private static final long serialVersionUID = 1L;
+	
+	/**
+	 * ACEGI often checks password ; but we do not have one. This is the replacement password 
+	 */
+	public static final String DEFAULT_PASSWORD = "";
+
+	/**
+	 * Create an NTLM {@link UsernamePasswordAuthenticationToken} using the
+	 * JCIFS {@link NtlmPasswordAuthentication} object.
+	 * 
+	 * @param ntlmAuth		The {@link NtlmPasswordAuthentication} object.
+	 * @param stripDomain	Uses just the username if <code>true</code>,
+	 * 						otherwise use the username and domain name.
+	 */
+	public NtlmUsernamePasswordAuthenticationToken(final NtlmPasswordAuthentication ntlmAuth, final boolean stripDomain) {
+		super((stripDomain) ? ntlmAuth.getUsername() : ntlmAuth.getName(), DEFAULT_PASSWORD);
+	}
+}

+ 108 - 0
ntlm/src/main/java/org/acegisecurity/ui/ntlm/ldap/authenticator/NtlmAwareLdapAuthenticationProvider.java

@@ -0,0 +1,108 @@
+/**
+ * 
+ */
+package org.acegisecurity.ui.ntlm.ldap.authenticator;
+
+import org.acegisecurity.*;
+import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+import org.acegisecurity.providers.ldap.LdapAuthenticationProvider;
+import org.acegisecurity.providers.ldap.LdapAuthoritiesPopulator;
+import org.acegisecurity.ui.ntlm.NtlmUsernamePasswordAuthenticationToken;
+import org.acegisecurity.userdetails.UserDetails;
+import org.acegisecurity.userdetails.ldap.LdapUserDetails;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.dao.DataAccessException;
+import org.springframework.util.StringUtils;
+import org.springframework.ldap.core.DirContextOperations;
+
+/**
+ * This provider implements specialized behaviour if the supplied {@link Authentication} object is
+ * from NTLM. In other cases calls the parent implementation.
+ * 
+ * @author sylvain.mougenot
+ * 
+ */
+public class NtlmAwareLdapAuthenticationProvider extends LdapAuthenticationProvider {
+	private static final Log logger = LogFactory.getLog(NtlmAwareLdapAuthenticationProvider.class);
+
+	/**
+	 * NTLM aware authenticator
+	 */
+	private NtlmAwareLdapAuthenticator authenticator;
+
+	/**
+	 * @param authenticator
+	 * @param authoritiesPopulator
+	 */
+	public NtlmAwareLdapAuthenticationProvider(NtlmAwareLdapAuthenticator authenticator,
+			                                    LdapAuthoritiesPopulator authoritiesPopulator) {
+		super(authenticator, authoritiesPopulator);
+		this.authenticator = authenticator;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.acegisecurity.providers.ldap.LdapAuthenticationProvider#retrieveUser(java.lang.String,
+	 *      org.acegisecurity.providers.UsernamePasswordAuthenticationToken)
+	 */
+	protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
+			throws AuthenticationException {
+		final UserDetails myDetails;
+
+		if (authentication instanceof NtlmUsernamePasswordAuthenticationToken) {
+			if (logger.isDebugEnabled()) {
+				logger.debug("Ntlm Token for Authentication"); //$NON-NLS-1$
+			}
+
+			// Only loads LDAP data
+			myDetails = retrieveUser(username, (NtlmUsernamePasswordAuthenticationToken) authentication);
+		} else {
+			// calls parent implementation
+			myDetails = super.retrieveUser(username, authentication);
+		}
+
+		return myDetails;
+	}
+
+	/**
+	 * Authentication has already been done. We need a particular behviour
+	 * because the parent check password consistency. But we do not have the
+	 * password (even if the user is authenticated).
+	 * 
+	 * @see NtlmUsernamePasswordAuthenticationToken#DEFAULT_PASSWORD
+	 * @param username
+	 * @param authentication
+	 * @return
+	 * @throws AuthenticationException
+	 */
+	protected UserDetails retrieveUser(String username, NtlmUsernamePasswordAuthenticationToken authentication)
+			throws AuthenticationException {
+		// identifiant obligatoire
+		if (!StringUtils.hasLength(username)) {
+			throw new BadCredentialsException(messages.getMessage(
+					"LdapAuthenticationProvider.emptyUsername",
+					"Empty Username"));
+		}
+
+		// NB: password is just the default value
+
+		if (logger.isDebugEnabled()) {
+			logger.debug("Retrieving user " + username);
+		}
+
+		try {
+			// Complies with our lack of password (can't bind)
+			DirContextOperations ldapUser = authenticator.authenticate(authentication);
+
+            GrantedAuthority[] extraAuthorities = getAuthoritiesPopulator().getGrantedAuthorities(ldapUser, username);
+
+            return getUserDetailsContextMapper().mapUserFromContext(ldapUser, username, extraAuthorities);
+
+		} catch (DataAccessException ldapAccessFailure) {
+			throw new AuthenticationServiceException(ldapAccessFailure
+					.getMessage(), ldapAccessFailure);
+		}
+	}
+}

+ 25 - 0
ntlm/src/main/java/org/acegisecurity/ui/ntlm/ldap/authenticator/NtlmAwareLdapAuthenticator.java

@@ -0,0 +1,25 @@
+/**
+ * 
+ */
+package org.acegisecurity.ui.ntlm.ldap.authenticator;
+
+import org.acegisecurity.providers.ldap.LdapAuthenticator;
+import org.acegisecurity.ui.ntlm.NtlmUsernamePasswordAuthenticationToken;
+import org.springframework.ldap.core.DirContextOperations;
+
+/**
+ * Authenticator compliant with NTLM part done previously (for authentication).
+ * 
+ * @author sylvain.mougenot
+ *
+ */
+public interface NtlmAwareLdapAuthenticator extends LdapAuthenticator {
+    /**
+     * Authentication was done previously by NTLM.
+     * Obtains additional user informations from the directory.
+     *
+     * @param aUserToken Ntlm issued authentication Token
+     * @return the details of the successfully authenticated user.
+     */
+    DirContextOperations authenticate(NtlmUsernamePasswordAuthenticationToken aUserToken);
+}

+ 121 - 0
ntlm/src/main/java/org/acegisecurity/ui/ntlm/ldap/authenticator/NtlmAwareLdapAuthenticatorImpl.java

@@ -0,0 +1,121 @@
+/**
+ * 
+ */
+package org.acegisecurity.ui.ntlm.ldap.authenticator;
+
+import java.util.Iterator;
+
+import org.acegisecurity.BadCredentialsException;
+import org.acegisecurity.Authentication;
+import org.acegisecurity.ldap.InitialDirContextFactory;
+import org.acegisecurity.ldap.SpringSecurityLdapTemplate;
+import org.acegisecurity.providers.ldap.authenticator.BindAuthenticator;
+import org.acegisecurity.ui.ntlm.NtlmUsernamePasswordAuthenticationToken;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.ldap.core.DirContextOperations;
+
+/**
+ * Fullfill the User details after NTLM authentication was done. Or (if no NTLM
+ * authentication done) act as the parent to authenticate the user
+ * 
+ * @author sylvain.mougenot
+ * 
+ */
+public class NtlmAwareLdapAuthenticatorImpl extends BindAuthenticator {
+	/**
+	 * Logger for this class
+	 */
+	private static final Log logger = LogFactory.getLog(NtlmAwareLdapAuthenticatorImpl.class);
+
+	/**
+	 * @param initialDirContextFactory
+	 */
+	public NtlmAwareLdapAuthenticatorImpl(InitialDirContextFactory initialDirContextFactory) {
+		super(initialDirContextFactory);
+	}
+
+	/**
+	 * Prepare the template without bind requirements.
+	 * 
+	 * @param aUserDn
+	 * @param aUserName
+	 * @see #loadDetail(SpringSecurityLdapTemplate, String, String)
+	 * @return
+	 */
+	protected DirContextOperations bindWithoutDn(String aUserDn, String aUserName) {
+		SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(getInitialDirContextFactory());
+		return loadDetail(template, aUserDn, aUserName);
+	}
+
+	/**
+	 * Load datas
+	 * 
+	 * @param aTemplate
+	 * @param aUserDn
+	 * @param aUserName
+	 * @return
+	 */
+	protected DirContextOperations loadDetail(SpringSecurityLdapTemplate aTemplate, String aUserDn, String aUserName) {
+		try {
+			DirContextOperations user =  aTemplate.retrieveEntry(aUserDn, getUserAttributes());
+
+			return user;
+		} catch (BadCredentialsException 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 (logger.isDebugEnabled()) {
+				logger.debug("Failed to bind as " + aUserDn + ": "
+						+ e.getMessage(), e);
+			}
+		}
+		return null;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.acegisecurity.ui.ntlm.NtlmAwareLdapAuthenticator#authenticate(org.acegisecurity.ui.ntlm.NtlmUsernamePasswordAuthenticationToken)
+	 */
+	public DirContextOperations authenticate(Authentication authentication) {
+        if (!(authentication instanceof NtlmUsernamePasswordAuthenticationToken)) {
+            return super.authenticate(authentication);
+        }
+
+        if (logger.isDebugEnabled()) {
+			logger.debug("authenticate(NtlmUsernamePasswordAuthenticationToken) - start"); //$NON-NLS-1$
+		}
+
+		final String userName = authentication.getName();
+		DirContextOperations user = null;
+
+		// If DN patterns are configured, try authenticating with them directly
+		Iterator myDns = getUserDns(userName).iterator();
+
+		// tries them all until we found something
+		while (myDns.hasNext() && (user == null)) {
+			user = bindWithoutDn((String) myDns.next(), userName);
+		}
+
+		// Otherwise use the configured locator to find the user
+		// and authenticate with the returned DN.
+		if ((user == null) && (getUserSearch() != null)) {
+			DirContextOperations userFromSearch = getUserSearch().searchForUser(userName);
+			// lancer l'identificvation
+			user = bindWithoutDn(userFromSearch.getDn().toString(), userName);
+		}
+
+		// Failed to locate the user in the LDAP directory
+		if (user == null) {
+			throw new BadCredentialsException(messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
+		}
+
+		if (logger.isDebugEnabled()) {
+			logger.debug("authenticate(NtlmUsernamePasswordAuthenticationToken) - end"); //$NON-NLS-1$
+		}
+		return user;
+	}
+}

+ 57 - 0
ntlm/web.xml

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
+	<display-name>Acegi NTLM</display-name>
+
+<!-- 1. Setup two parameters:          -->
+<!--    a) Acegi's configuration file  -->
+<!--    b) Loggin configuration file   -->
+	<context-param>
+		<param-name>contextConfigLocation</param-name>
+		<param-value>/WEB-INF/applicationContext.xml</param-value>
+	</context-param>
+
+	<context-param>
+		<param-name>log4jConfigLocation</param-name>
+		<param-value>/WEB-INF/log4j.properties</param-value>
+	</context-param>
+
+<!-- 2. Setup the Acegi Filter Chain Proxy -->
+	<filter>
+		<filter-name>Acegi Filter Chain Proxy</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 Filter Chain Proxy</filter-name>
+		<url-pattern>/**</url-pattern>
+	</filter-mapping>
+
+<!-- 3. Setup three listeners -->
+<!--    a) Setup a listener to connect spring with the web context -->
+	<listener>
+		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+	</listener>
+
+<!--	b) Setup a listener to connect spring with log4J --> 
+	<listener>
+		<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
+	</listener>
+
+<!--    c) Setup ACEGI to subscribe to http session events in the web context -->
+	<listener>
+		<listener-class>org.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>
+	</listener>
+
+	<welcome-file-list>
+		<welcome-file>index.html</welcome-file>
+		<welcome-file>index.htm</welcome-file>
+		<welcome-file>index.jsp</welcome-file>
+		<welcome-file>default.html</welcome-file>
+		<welcome-file>default.htm</welcome-file>
+		<welcome-file>default.jsp</welcome-file>
+	</welcome-file-list>
+</web-app>