فهرست منبع

OPEN - issue SEC-865: Re-Challenge NTLM Clients after Authentication Failure
http://jira.springframework.org/browse/SEC-865. Changed NTLM filter to re-challenge if retryOnAuthFailure is set and the Smb logon call fails. Updated JCIFS version in pom.

Luke Taylor 17 سال پیش
والد
کامیت
827d0e1ebf
2فایلهای تغییر یافته به همراه446 افزوده شده و 439 حذف شده
  1. 44 44
      ntlm/pom.xml
  2. 402 395
      ntlm/src/main/java/org/springframework/security/ui/ntlm/NtlmProcessingFilter.java

+ 44 - 44
ntlm/pom.xml

@@ -1,36 +1,36 @@
 <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.springframework.security</groupId>
-		<artifactId>spring-security-parent</artifactId>
-		<version>2.0.4-SNAPSHOT</version>
-	</parent>
-	<packaging>jar</packaging>
-	<artifactId>spring-security-ntlm</artifactId>
-	<name>Spring Security - NTLM support</name>
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.security</groupId>
+        <artifactId>spring-security-parent</artifactId>
+        <version>2.0.4-SNAPSHOT</version>
+    </parent>
+    <packaging>jar</packaging>
+    <artifactId>spring-security-ntlm</artifactId>
+    <name>Spring Security - NTLM support</name>
 
-	<dependencies>
-		<dependency>
-	      <groupId>org.springframework.security</groupId>
-	      <artifactId>spring-security-core</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>
-		</dependency>
+    <dependencies>
+        <dependency>
+          <groupId>org.springframework.security</groupId>
+          <artifactId>spring-security-core</artifactId>
+          <version>${project.version}</version>
+        </dependency>
+        <!-- SMT NTLM-->
+        <dependency>
+            <groupId>org.samba.jcifs</groupId>
+            <artifactId>jcifs</artifactId>
+            <version>1.2.19</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>
+        </dependency>
         <dependency>
             <groupId>org.springframework.ldap</groupId>
             <artifactId>spring-ldap</artifactId>
@@ -38,17 +38,17 @@
         </dependency>
     </dependencies>
 
-	<build>
-		<resources>
-			<resource>
-				<directory>${basedir}/src/main/resources</directory>
-				<targetPath>/</targetPath>
-				<includes>
-					<include>**/*</include>
-				</includes>
-				<filtering>false</filtering>
-			</resource>
-		</resources>
-	</build>
+    <build>
+        <resources>
+            <resource>
+                <directory>${basedir}/src/main/resources</directory>
+                <targetPath>/</targetPath>
+                <includes>
+                    <include>**/*</include>
+                </includes>
+                <filtering>false</filtering>
+            </resource>
+        </resources>
+    </build>
 
-</project>
+</project>

+ 402 - 395
ntlm/src/main/java/org/springframework/security/ui/ntlm/NtlmProcessingFilter.java

@@ -82,221 +82,222 @@ import java.util.Properties;
  * @version $Id$
  */
 public class NtlmProcessingFilter extends SpringSecurityFilter implements InitializingBean {
-	//~ Static fields/initializers =====================================================================================
+    //~ Static fields/initializers =====================================================================================
 
-	private static Log	logger = LogFactory.getLog(NtlmProcessingFilter.class);
+    private static Log	logger = LogFactory.getLog(NtlmProcessingFilter.class);
 
-	private static final String	STATE_ATTR = "SpringSecurityNtlm";
-	private static final String	CHALLENGE_ATTR = "NtlmChal";
-	private static final Integer BEGIN = new Integer(0);
-	private static final Integer NEGOTIATE = new Integer(1);
-	private static final Integer COMPLETE = new Integer(2);
-	private static final Integer DELAYED = new Integer(3);
+    private static final String	STATE_ATTR = "SpringSecurityNtlm";
+    private static final String	CHALLENGE_ATTR = "NtlmChal";
+    private static final Integer BEGIN = new Integer(0);
+    private static final Integer NEGOTIATE = new Integer(1);
+    private static final Integer COMPLETE = new Integer(2);
+    private static final Integer DELAYED = new Integer(3);
 
     //~ Instance fields ================================================================================================
 
-	/** Should the filter load balance among multiple domain controllers, default <code>false</code> */
-	private boolean	loadBalance;
+    /** Should the filter load balance among multiple domain controllers, default <code>false</code> */
+    private boolean	loadBalance;
 
-	/** Should the domain name be stripped from the username, default <code>true</code> */
-	private boolean stripDomain = true;
+    /** Should 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;
+    /** Should the filter initiate NTLM negotiations, default <code>true</code>	*/
+    private boolean forceIdentification = true;
 
-	/** Should the filter retry NTLM on authorization failure, default <code>false</code> */
-	private boolean retryOnAuthFailure;
+    /** Should 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;
+    private String	soTimeout;
+    private String	cachePolicy;
+    private String	defaultDomain;
+    private String	domainController;
+    private AuthenticationManager authenticationManager;
     private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource();
 
     //~ 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));
-			}
+    /**
+     * 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;
-	}
+    }
+
+    /**
+     * 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 occur if the credentials are rejected by the domain controller or if an
+     * an {@link AuthenticationCredentialsNotFoundException}
+     * or {@link InsufficientAuthenticationException} is thrown.
+     *
+     * @param retryOnFailure the retry on failure flag value to set.
+     */
+    public void setRetryOnAuthFailure(boolean retryOnFailure) {
+        this.retryOnAuthFailure = retryOnFailure;
+    }
 
     public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) {
         Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null");
@@ -305,207 +306,213 @@ public class NtlmProcessingFilter extends SpringSecurityFilter implements Initia
 
     protected void doFilterHttp(final HttpServletRequest request,
             final HttpServletResponse response, final FilterChain chain) throws IOException, ServletException {
-		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 Spring Security 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);
-				}
-			}
-		}
+        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 Spring Security 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);
+                }
+            }
+        }
 
         chain.doFilter(request, response);
     }
 
-	/**
-	 * 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 Spring
-	 * 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(authenticationDetailsSource.buildDetails(request));
-
-		// Place the last username attempted into HttpSession for views
-		session.setAttribute(AuthenticationProcessingFilter.SPRING_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;
+    /**
+     * 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);
+
+            if (retryOnAuthFailure) {
+                logger.debug("Restarting NTLM authentication handshake");
+                session.setAttribute(STATE_ATTR, BEGIN);
+                throw new NtlmBeginHandshakeException();
+            }
+
+            throw new BadCredentialsException("Bad NTLM credentials");
+        } finally {
+            session.removeAttribute(CHALLENGE_ATTR);
+        }
+    }
+
+    /**
+     * Authenticates the user credentials acquired from NTLM against the Spring
+     * 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(authenticationDetailsSource.buildDetails(request));
+
+        // Place the last username attempted into HttpSession for views
+        session.setAttribute(AuthenticationProcessingFilter.SPRING_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);
-	}
+    }
 
     public int getOrder() {
         return FilterChainOrder.NTLM_FILTER;