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.
This commit is contained in:
parent
55d357f42d
commit
827d0e1ebf
88
ntlm/pom.xml
88
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>
|
||||
|
|
|
@ -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");
|
||||
/**
|
||||
* 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);
|
||||
// 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));
|
||||
}
|
||||
if (domainController == null) {
|
||||
domainController = defaultDomain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 the <code>AuthenticationManager</code> to use.
|
||||
*
|
||||
* @param authenticationManager the <code>AuthenticationManager</code> to use.
|
||||
*/
|
||||
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
|
||||
this.authenticationManager = authenticationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
/**
|
||||
* 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 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;
|
||||
}
|
||||
/**
|
||||
* 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 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
/**
|
||||
* 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()));
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
if (retryOnAuthFailure) {
|
||||
logger.debug("Restarting NTLM authentication handshake");
|
||||
session.setAttribute(STATE_ATTR, BEGIN);
|
||||
throw new NtlmBeginHandshakeException();
|
||||
}
|
||||
|
||||
authRequest = new NtlmUsernamePasswordAuthenticationToken(auth, stripDomain);
|
||||
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
|
||||
throw new BadCredentialsException("Bad NTLM credentials");
|
||||
} finally {
|
||||
session.removeAttribute(CHALLENGE_ATTR);
|
||||
}
|
||||
}
|
||||
|
||||
// Place the last username attempted into HttpSession for views
|
||||
session.setAttribute(AuthenticationProcessingFilter.SPRING_SECURITY_LAST_USERNAME_KEY, authRequest.getName());
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// Backup the current authentication in case of an AuthenticationException
|
||||
backupAuth = SecurityContextHolder.getContext().getAuthentication();
|
||||
authRequest = new NtlmUsernamePasswordAuthenticationToken(auth, stripDomain);
|
||||
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
|
||||
|
||||
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());
|
||||
}
|
||||
// Place the last username attempted into HttpSession for views
|
||||
session.setAttribute(AuthenticationProcessingFilter.SPRING_SECURITY_LAST_USERNAME_KEY, authRequest.getName());
|
||||
|
||||
// Reset the backup Authentication object and rethrow the AuthenticationException
|
||||
SecurityContextHolder.getContext().setAuthentication(backupAuth);
|
||||
// Backup the current authentication in case of an AuthenticationException
|
||||
backupAuth = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
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();
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
throw failed;
|
||||
}
|
||||
// Reset the backup Authentication object and rethrow the AuthenticationException
|
||||
SecurityContextHolder.getContext().setAuthentication(backupAuth);
|
||||
|
||||
// Set the Authentication object with the valid authentication result
|
||||
SecurityContextHolder.getContext().setAuthentication(authResult);
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
throw failed;
|
||||
}
|
||||
|
||||
return UniAddress.getByName(domainController, true);
|
||||
}
|
||||
// Set the Authentication object with the valid authentication result
|
||||
SecurityContextHolder.getContext().setAuthentication(authResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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;
|
||||
|
|
Loading…
Reference in New Issue