SEC-1233: Removed NTLM support for 3.0

This commit is contained in:
Luke Taylor 2009-09-13 21:55:14 +00:00
parent 40cf50fc98
commit d52a806a1d
12 changed files with 0 additions and 1173 deletions

View File

@ -1,5 +0,0 @@
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.

View File

@ -1,95 +0,0 @@
<?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.springframework.security.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.springframework.security.core.context.HttpSessionContextIntegrationFilter">
<property name="context">
<value>org.springframework.security.core.context.SecurityContextImpl</value>
</property>
</bean>
<!-- the second item in the chain: exceptionTranslationFilter -->
<bean id="exceptionTranslationFilter" class="org.springframework.security.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint" ref="ntlmEntryPoint"/>
</bean>
<!-- the third item in the chain: ntlmFilter -->
<bean id="ntlmFilter" class="org.springframework.security.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.springframework.security.authentication.ProviderManager">
<property name="providers">
<list>
<ref local="daoAuthenticationProvider"/>
</list>
</property>
</bean>
<bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.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.springframework.security.core.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.springframework.security.access.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager"><ref local="providerManager"/></property>
<property name="accessDecisionManager"><ref local="accessDecisionManager"/></property>
<property name="securityMetadataSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=ROLE_USER
</value>
</property>
</bean>
<!-- authenticationManager defined above -->
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased">
<property name="allowIfAllAbstainDecisions">
<value>false</value>
</property>
<property name="decisionVoters">
<list>
<ref local="roleVoter"/>
</list>
</property>
</bean>
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter"/>
<bean id="ntlmEntryPoint" class="org.springframework.security.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.springframework.security.authentication.event.LoggerListener"/>
</beans>

View File

@ -1,64 +0,0 @@
<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>3.0.0.CI-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>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</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-core</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
<targetPath>/</targetPath>
<includes>
<include>**/*</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>

View File

@ -1,34 +0,0 @@
/* 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.springframework.security.ui.ntlm;
import org.springframework.security.core.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);
}
}

View File

@ -1,29 +0,0 @@
/* 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.springframework.security.ui.ntlm;
/**
* Signals the beginning of an NTLM handshaking process.
*
* @author Edward Smith
*/
public class NtlmBeginHandshakeException extends NtlmBaseException {
public NtlmBeginHandshakeException() {
super("NTLM");
}
}

View File

@ -1,520 +0,0 @@
/* 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.springframework.security.ui.ntlm;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Properties;
import javax.servlet.FilterChain;
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.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;
/**
* A clean-room implementation for Spring Security 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 automatically 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, a Spring
* 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 Spring 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 GenericFilterBean {
//~ Static fields/initializers =====================================================================================
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);
//~ Instance fields ================================================================================================
/** 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 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;
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.
*/
@Override
public void afterPropertiesSet() {
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 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");
this.authenticationDetailsSource = authenticationDetailsSource;
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
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);
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(UsernamePasswordAuthenticationProcessingFilter.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);
}
}

View File

@ -1,108 +0,0 @@
/* 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.springframework.security.ui.ntlm;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.util.Assert;
/**
* 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 {
//~ Instance fields ================================================================================================
/** Where to redirect the browser to if authentication fails */
private String authenticationFailureUrl;
//~ Methods ========================================================================================================
/**
* Sets the authentication failure URL.
*
* @param authenticationFailureUrl the authentication failure URL.
*/
public void setAuthenticationFailureUrl(String authenticationFailureUrl) {
Assert.hasLength(authenticationFailureUrl, "authenticationFailureUrl must be specified");
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 HttpServletRequest request, final HttpServletResponse 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();
return;
}
if (authenticationFailureUrl == null) {
if (!response.isCommitted()) {
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN, authException.getMessage());
}
} else {
String url = authenticationFailureUrl;
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = ((HttpServletRequest) request).getContextPath() + url;
}
resp.sendRedirect(resp.encodeRedirectURL(url));
}
}
}

View File

@ -1,48 +0,0 @@
/* 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.springframework.security.ui.ntlm;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.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);
}
}
}

View File

@ -1,59 +0,0 @@
/* 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.springframework.security.ui.ntlm;
import java.util.List;
import jcifs.smb.NtlmPasswordAuthentication;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
/**
* 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;
/**
* Dummy authority array which is passed to the constructor of the parent class,
* ensuring that the "authenticated" property is set to "true" by default. See SEC-609.
*/
private static final List<GrantedAuthority> NTLM_AUTHENTICATED =
AuthorityUtils.createAuthorityList("NTLM_AUTHENTICATED");
/**
* Spring Security 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(NtlmPasswordAuthentication ntlmAuth, boolean stripDomain) {
super((stripDomain) ? ntlmAuth.getUsername() : ntlmAuth.getName(), DEFAULT_PASSWORD, NTLM_AUTHENTICATED);
}
}

View File

@ -1,108 +0,0 @@
/**
*
*/
package org.springframework.security.ui.ntlm.ldap.authenticator;
import java.util.Iterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ldap.NameNotFoundException;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.security.ldap.authentication.BindAuthenticator;
import org.springframework.security.ui.ntlm.NtlmUsernamePasswordAuthenticationToken;
/**
* Loads the UserDetails if authentication was already performed by NTLM (indicated by the type of authentication
* token submitted). Otherwise falls back to the parent class behaviour, attempting to bind as the user.
*
* @author sylvain.mougenot
*
*/
public class NtlmAwareLdapAuthenticator extends BindAuthenticator {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(NtlmAwareLdapAuthenticator.class);
//~ Constructors ===================================================================================================
public NtlmAwareLdapAuthenticator(BaseLdapPathContextSource contextSource) {
super(contextSource);
}
//~ Methods ========================================================================================================
/**
* Loads the user context information without binding.
*/
protected DirContextOperations loadUser(String aUserDn, String aUserName) {
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(getContextSource());
try {
DirContextOperations user = template.retrieveEntry(aUserDn, getUserAttributes());
return user;
} catch (NameNotFoundException 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.
if (logger.isDebugEnabled()) {
logger.debug("Failed to load user " + aUserDn + ": " + e.getMessage(), e);
}
}
return null;
}
/**
* If the supplied <tt>Authentication</tt> object is of type <tt>NtlmUsernamePasswordAuthenticationToken</tt>,
* the information stored in the user's directory entry is loaded without attempting to authenticate them.
* Otherwise the parent class is called to perform a bind operation to authenticate the user.
*/
public DirContextOperations authenticate(Authentication authentication) {
if (!(authentication instanceof NtlmUsernamePasswordAuthenticationToken)) {
// Not NTLM authenticated, so call the base class to authenticate the user.
return super.authenticate(authentication);
}
if (!authentication.isAuthenticated()) {
throw new BadCredentialsException("Unauthenticated NTLM authentication token found");
}
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 = loadUser((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 = loadUser(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;
}
}

View File

@ -1,50 +0,0 @@
package org.springframework.security.ui.ntlm.ldap.authenticator;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ui.ntlm.NtlmUsernamePasswordAuthenticationToken;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import jcifs.smb.NtlmPasswordAuthentication;
import org.junit.Test;
/**
* @author Luke Taylor
* @version $Id$
*/
public class NtlmAwareLdapAuthenticatorTests {
/**
* See SEC-609.
*/
@Test(expected = BadCredentialsException.class)
public void unauthenticatedTokenIsRejected() {
NtlmAwareLdapAuthenticator authenticator = new NtlmAwareLdapAuthenticator(
new DefaultSpringSecurityContextSource("ldap://blah"));
NtlmUsernamePasswordAuthenticationToken token = new NtlmUsernamePasswordAuthenticationToken(
new NtlmPasswordAuthentication("blah"), false);
token.setAuthenticated(false);
authenticator.authenticate(token);
}
@Test
public void authenticatedTokenIsAccepted() {
NtlmAwareLdapAuthenticator authenticator = new NtlmAwareLdapAuthenticator(new DefaultSpringSecurityContextSource("ldap://blah")) {
// mimic loading of user
protected DirContextOperations loadUser(String aUserDn, String aUserName) {
return new DirContextAdapter();
}
};
authenticator.setUserDnPatterns(new String[] {"somepattern"});
NtlmUsernamePasswordAuthenticationToken token = new NtlmUsernamePasswordAuthenticationToken(
new NtlmPasswordAuthentication("blah"), false);
authenticator.authenticate(token);
}
}

View File

@ -1,53 +0,0 @@
<?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>Spring Security NTLM</display-name>
<!-- 1. Setup two parameters: -->
<!-- a) Spring Security's configuration file -->
<!-- b) Logging 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 Spring Security Filter Chain Proxy -->
<filter>
<filter-name>filterChainProxy</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>filterChainProxy</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 Spring Security to subscribe to http session events in the web context -->
<listener>
<listener-class>org.springframework.security.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>