SEC-8: Imported contributed NTLM code. Some changes because of current state of LDAP code.
This commit is contained in:
parent
c7354c125a
commit
8762ffabbe
|
@ -0,0 +1,5 @@
|
|||
Just place this folder into the SVN checkout of ACEGI sources.
|
||||
Then modify the root pom.xml to include the folder as a module.
|
||||
|
||||
The applicationContext.xml and web.xml files are included in
|
||||
the root directory for example purposes only.
|
|
@ -0,0 +1,95 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
|
||||
|
||||
<beans>
|
||||
|
||||
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
|
||||
<property name="filterInvocationDefinitionSource">
|
||||
<value>
|
||||
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
|
||||
PATTERN_TYPE_APACHE_ANT
|
||||
/login_error.jsp=httpSessionContextIntegrationFilter
|
||||
/**=httpSessionContextIntegrationFilter, exceptionTranslationFilter, ntlmFilter, filterSecurityInterceptor
|
||||
</value>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<!-- The first item in the Chain: httpSessionContextIntegrationFilter -->
|
||||
<bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter">
|
||||
<property name="context">
|
||||
<value>org.acegisecurity.context.SecurityContextImpl</value>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<!-- the second item in the chain: exceptionTranslationFilter -->
|
||||
<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
|
||||
<property name="authenticationEntryPoint" ref="ntlmEntryPoint"/>
|
||||
</bean>
|
||||
|
||||
<!-- the third item in the chain: ntlmFilter -->
|
||||
<bean id="ntlmFilter" class="org.acegisecurity.ui.ntlm.NtlmProcessingFilter">
|
||||
<property name="defaultDomain" value="YOURDOMAIN"/>
|
||||
<!-- It is better to use a WINS server if available over a specific domain controller
|
||||
<property name="domainController" value="FOO"/> -->
|
||||
<property name="netbiosWINS" value="192.168.0.3"/>
|
||||
<property name="authenticationManager" ref="providerManager"/>
|
||||
</bean>
|
||||
|
||||
<bean id="providerManager" class="org.acegisecurity.providers.ProviderManager">
|
||||
<property name="providers">
|
||||
<list>
|
||||
<ref local="daoAuthenticationProvider"/>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
|
||||
<property name="userDetailsService">
|
||||
<ref local="memoryUserDetailsService"/>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<!-- NOTE: You will need to write a custom UserDetailsService in most cases -->
|
||||
<bean id="memoryUserDetailsService" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
|
||||
<property name="userMap">
|
||||
<value>jdoe=PASSWORD,ROLE_USER</value>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<!-- the fourth item in the chain: filterSecurityInterceptor -->
|
||||
<bean id="filterSecurityInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
|
||||
<property name="authenticationManager"><ref local="providerManager"/></property>
|
||||
<property name="accessDecisionManager"><ref local="accessDecisionManager"/></property>
|
||||
<property name="objectDefinitionSource">
|
||||
<value>
|
||||
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
|
||||
PATTERN_TYPE_APACHE_ANT
|
||||
/**=ROLE_USER
|
||||
</value>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<!-- authenticationManager defined above -->
|
||||
<bean id="accessDecisionManager" class="org.acegisecurity.vote.UnanimousBased">
|
||||
<property name="allowIfAllAbstainDecisions">
|
||||
<value>false</value>
|
||||
</property>
|
||||
<property name="decisionVoters">
|
||||
<list>
|
||||
<ref local="roleVoter"/>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter"/>
|
||||
|
||||
<bean id="ntlmEntryPoint" class="org.acegisecurity.ui.ntlm.NtlmProcessingFilterEntryPoint">
|
||||
<property name="authenticationFailureUrl" value="/login_error.jsp"/>
|
||||
</bean>
|
||||
|
||||
<!-- Done with the chain -->
|
||||
|
||||
<!-- This bean automatically receives AuthenticationEvent messages from DaoAuthenticationProvider -->
|
||||
<bean id="loggerListener" class="org.acegisecurity.event.authentication.LoggerListener"/>
|
||||
|
||||
</beans>
|
|
@ -0,0 +1,65 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.acegisecurity</groupId>
|
||||
<artifactId>acegi-security-parent</artifactId>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<packaging>jar</packaging>
|
||||
<artifactId>spring-security-ntlm</artifactId>
|
||||
<name>Spring Security - NTLM</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.acegisecurity</groupId>
|
||||
<artifactId>acegi-security</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<!-- SMT NTLM-->
|
||||
<dependency>
|
||||
<groupId>org.samba.jcifs</groupId>
|
||||
<artifactId>jcifs</artifactId>
|
||||
<version>1.2.15</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>jsp-api</artifactId>
|
||||
<version>2.0</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<version>2.4</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ldap</groupId>
|
||||
<artifactId>spring-ldap</artifactId>
|
||||
<version>1.2-RC1</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${basedir}/../</directory>
|
||||
<targetPath>META-INF</targetPath>
|
||||
<includes>
|
||||
<include>notice.txt</include>
|
||||
</includes>
|
||||
<filtering>false</filtering>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>${basedir}/src/main/resources</directory>
|
||||
<targetPath>/</targetPath>
|
||||
<includes>
|
||||
<include>**/*</include>
|
||||
</includes>
|
||||
<filtering>false</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,61 @@
|
|||
/* Copyright 2004-2007 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.acegisecurity.ui.ntlm;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public abstract class HttpFilter implements Filter {
|
||||
|
||||
public void init(FilterConfig config) throws ServletException {}
|
||||
|
||||
public void destroy() {}
|
||||
|
||||
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
|
||||
if (!(request instanceof HttpServletRequest)) {
|
||||
throw new ServletException("Can only process HttpServletRequest");
|
||||
}
|
||||
|
||||
if (!(response instanceof HttpServletResponse)) {
|
||||
throw new ServletException("Can only process HttpServletResponse");
|
||||
}
|
||||
|
||||
this.doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
// *************************** Protected Methods ****************************
|
||||
|
||||
protected abstract void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException;
|
||||
|
||||
protected void sendRedirect(final HttpServletRequest request, final HttpServletResponse response, String url) throws IOException {
|
||||
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
||||
url = request.getContextPath() + url;
|
||||
}
|
||||
|
||||
response.sendRedirect(response.encodeRedirectURL(url));
|
||||
}
|
||||
|
||||
} // End HttpFilter
|
|
@ -0,0 +1,34 @@
|
|||
/* Copyright 2004-2007 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.acegisecurity.ui.ntlm;
|
||||
|
||||
import org.acegisecurity.AuthenticationException;
|
||||
|
||||
/**
|
||||
* Base class for NTLM exceptions so that it is easier to distinguish them
|
||||
* from other <code>AuthenticationException</code>s in the
|
||||
* {@link NtlmProcessingFilterEntryPoint}. Marked as <code>abstract</code>
|
||||
* since this exception is never supposed to be instantiated.
|
||||
*
|
||||
* @author Edward Smith
|
||||
*/
|
||||
public abstract class NtlmBaseException extends AuthenticationException {
|
||||
|
||||
public NtlmBaseException(final String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
} // End NtlmBaseException
|
|
@ -0,0 +1,31 @@
|
|||
/* Copyright 2004-2007 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.acegisecurity.ui.ntlm;
|
||||
|
||||
import org.acegisecurity.AuthenticationException;
|
||||
|
||||
/**
|
||||
* Signals the beginning of an NTLM handshaking process.
|
||||
*
|
||||
* @author Edward Smith
|
||||
*/
|
||||
public class NtlmBeginHandshakeException extends NtlmBaseException {
|
||||
|
||||
public NtlmBeginHandshakeException() {
|
||||
super("NTLM");
|
||||
}
|
||||
|
||||
} // End NtlmBeginHandshakeException
|
|
@ -0,0 +1,511 @@
|
|||
/* Copyright 2004-2007 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.acegisecurity.ui.ntlm;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import jcifs.Config;
|
||||
import jcifs.UniAddress;
|
||||
import jcifs.ntlmssp.Type1Message;
|
||||
import jcifs.ntlmssp.Type2Message;
|
||||
import jcifs.ntlmssp.Type3Message;
|
||||
import jcifs.smb.NtlmChallenge;
|
||||
import jcifs.smb.NtlmPasswordAuthentication;
|
||||
import jcifs.smb.SmbAuthException;
|
||||
import jcifs.smb.SmbException;
|
||||
import jcifs.smb.SmbSession;
|
||||
import jcifs.util.Base64;
|
||||
|
||||
import org.acegisecurity.AcegiMessageSource;
|
||||
import org.acegisecurity.Authentication;
|
||||
import org.acegisecurity.AuthenticationCredentialsNotFoundException;
|
||||
import org.acegisecurity.AuthenticationException;
|
||||
import org.acegisecurity.AuthenticationManager;
|
||||
import org.acegisecurity.BadCredentialsException;
|
||||
import org.acegisecurity.InsufficientAuthenticationException;
|
||||
import org.acegisecurity.context.SecurityContextHolder;
|
||||
import org.acegisecurity.event.authentication.InteractiveAuthenticationSuccessEvent;
|
||||
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
|
||||
import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
|
||||
import org.acegisecurity.ui.AbstractProcessingFilter;
|
||||
import org.acegisecurity.ui.WebAuthenticationDetails;
|
||||
import org.acegisecurity.ui.savedrequest.SavedRequest;
|
||||
import org.acegisecurity.ui.webapp.AuthenticationProcessingFilter;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A clean-room implementation for Acegi Security System of an NTLM HTTP filter
|
||||
* leveraging the JCIFS library.
|
||||
* <p>
|
||||
* NTLM is a Microsoft-developed protocol providing single sign-on capabilities
|
||||
* to web applications and other integrated applications. It allows a web
|
||||
* server to automatcially discover the username of a browser client when that
|
||||
* client is logged into a Windows domain and is using an NTLM-aware browser.
|
||||
* A web application can then reuse the user's Windows credentials without
|
||||
* having to ask for them again.
|
||||
* <p>
|
||||
* Because NTLM only provides the username of the Windows client, an Acegi
|
||||
* Security NTLM deployment must have a <code>UserDetailsService</code> that
|
||||
* provides a <code>UserDetails</code> object with the empty string as the
|
||||
* password and whatever <code>GrantedAuthority</code> values necessary to
|
||||
* pass the <code>FilterSecurityInterceptor</code>.
|
||||
* <p>
|
||||
* The Acegi Security bean configuration file must also place the
|
||||
* <code>ExceptionTranslationFilter</code> before this filter in the
|
||||
* <code>FilterChainProxy</code> definition.
|
||||
*
|
||||
* @author Davide Baroncelli
|
||||
* @author Edward Smith
|
||||
* @version $Id$
|
||||
*/
|
||||
public class NtlmProcessingFilter extends HttpFilter implements InitializingBean {
|
||||
//~ Static fields/initializers =====================================================================================
|
||||
|
||||
private static Log logger = LogFactory.getLog(NtlmProcessingFilter.class);
|
||||
|
||||
private static final String STATE_ATTR = "AcegiNtlm";
|
||||
private static final String CHALLENGE_ATTR = "NtlmChal";
|
||||
private static final Integer BEGIN = Integer.valueOf(0);
|
||||
private static final Integer NEGOTIATE = Integer.valueOf(1);
|
||||
private static final Integer COMPLETE = Integer.valueOf(2);
|
||||
private static final Integer DELAYED = Integer.valueOf(3);
|
||||
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
/** Shoud the filter load balance among multiple domain controllers, default <code>false</code> */
|
||||
private boolean loadBalance;
|
||||
|
||||
/** Shoud the domain name be stripped from the username, default <code>true</code> */
|
||||
private boolean stripDomain = true;
|
||||
|
||||
/** Should the filter initiate NTLM negotiations, default <code>true</code> */
|
||||
private boolean forceIdentification = true;
|
||||
|
||||
/** Shoud the filter retry NTLM on authorization failure, default <code>false</code> */
|
||||
private boolean retryOnAuthFailure;
|
||||
|
||||
private String soTimeout;
|
||||
private String cachePolicy;
|
||||
private String defaultDomain;
|
||||
private String domainController;
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
//~ Public Methods =================================================================================================
|
||||
|
||||
/**
|
||||
* Ensures an <code>AuthenticationManager</code> and authentication failure
|
||||
* URL have been provided in the bean configuration file.
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(this.authenticationManager, "An AuthenticationManager is required");
|
||||
|
||||
// Default to 5 minutes if not already specified
|
||||
Config.setProperty("jcifs.smb.client.soTimeout", (soTimeout == null) ? "300000" : soTimeout);
|
||||
// Default to 20 minutes if not already specified
|
||||
Config.setProperty("jcifs.netbios.cachePolicy", (cachePolicy == null) ? "1200" : cachePolicy);
|
||||
|
||||
if (domainController == null) {
|
||||
domainController = defaultDomain;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the <code>AuthenticationManager</code> to use.
|
||||
*
|
||||
* @param authenticationManager the <code>AuthenticationManager</code> to use.
|
||||
*/
|
||||
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
|
||||
this.authenticationManager = authenticationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* The NT domain against which clients should be authenticated. If the SMB
|
||||
* client username and password are also set, then preauthentication will
|
||||
* be used which is necessary to initialize the SMB signing digest. SMB
|
||||
* signatures are required by default on Windows 2003 domain controllers.
|
||||
*
|
||||
* @param defaultDomain The name of the default domain.
|
||||
*/
|
||||
public void setDefaultDomain(String defaultDomain) {
|
||||
this.defaultDomain = defaultDomain;
|
||||
Config.setProperty("jcifs.smb.client.domain", defaultDomain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the SMB client username.
|
||||
*
|
||||
* @param smbClientUsername The SMB client username.
|
||||
*/
|
||||
public void setSmbClientUsername(String smbClientUsername) {
|
||||
Config.setProperty("jcifs.smb.client.username", smbClientUsername);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the SMB client password.
|
||||
*
|
||||
* @param smbClientPassword The SMB client password.
|
||||
*/
|
||||
public void setSmbClientPassword(String smbClientPassword) {
|
||||
Config.setProperty("jcifs.smb.client.password", smbClientPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the SMB client SSN limit. When set to <code>1</code>, every
|
||||
* authentication is forced to use a separate transport. This effectively
|
||||
* ignores SMB signing requirements, however at the expense of reducing
|
||||
* scalability. Preauthentication with a domain, username, and password is
|
||||
* the preferred method for working with servers that require signatures.
|
||||
*
|
||||
* @param smbClientSSNLimit The SMB client SSN limit.
|
||||
*/
|
||||
public void setSmbClientSSNLimit(String smbClientSSNLimit) {
|
||||
Config.setProperty("jcifs.smb.client.ssnLimit", smbClientSSNLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures JCIFS to use a WINS server. It is preferred to use a WINS
|
||||
* server over a specific domain controller. Set this property instead of
|
||||
* <code>domainController</code> if there is a WINS server available.
|
||||
*
|
||||
* @param netbiosWINS The WINS server JCIFS will use.
|
||||
*/
|
||||
public void setNetbiosWINS(String netbiosWINS) {
|
||||
Config.setProperty("jcifs.netbios.wins", netbiosWINS);
|
||||
}
|
||||
|
||||
/**
|
||||
* The IP address of any SMB server that should be used to authenticate
|
||||
* HTTP clients.
|
||||
*
|
||||
* @param domainController The IP address of the domain controller.
|
||||
*/
|
||||
public void setDomainController(String domainController) {
|
||||
this.domainController = domainController;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the default domain is specified and the domain controller is not
|
||||
* specified, then query for domain controllers by name. When load
|
||||
* balance is <code>true</code>, rotate through the list of domain
|
||||
* controllers when authenticating users.
|
||||
*
|
||||
* @param loadBalance The load balance flag value.
|
||||
*/
|
||||
public void setLoadBalance(boolean loadBalance) {
|
||||
this.loadBalance = loadBalance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures <code>NtlmProcessingFilter</code> to strip the Windows
|
||||
* domain name from the username when set to <code>true</code>, which
|
||||
* is the default value.
|
||||
*
|
||||
* @param stripDomain The strip domain flag value.
|
||||
*/
|
||||
public void setStripDomain(boolean stripDomain) {
|
||||
this.stripDomain = stripDomain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the <code>jcifs.smb.client.soTimeout</code> property to the
|
||||
* timeout value specified in milliseconds. Defaults to 5 minutes
|
||||
* if not specified.
|
||||
*
|
||||
* @param timeout The milliseconds timeout value.
|
||||
*/
|
||||
public void setSoTimeout(String timeout) {
|
||||
this.soTimeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the <code>jcifs.netbios.cachePolicy</code> property to the
|
||||
* number of seconds a NetBIOS address is cached by JCIFS. Defaults to
|
||||
* 20 minutes if not specified.
|
||||
*
|
||||
* @param numSeconds The number of seconds a NetBIOS address is cached.
|
||||
*/
|
||||
public void setCachePolicy(String numSeconds) {
|
||||
this.cachePolicy = numSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads properties starting with "jcifs" into the JCIFS configuration.
|
||||
* Any other properties are ignored.
|
||||
*
|
||||
* @param props The JCIFS properties to set.
|
||||
*/
|
||||
public void setJcifsProperties(Properties props) {
|
||||
String name;
|
||||
|
||||
for (Enumeration e=props.keys(); e.hasMoreElements();) {
|
||||
name = (String) e.nextElement();
|
||||
if (name.startsWith("jcifs.")) {
|
||||
Config.setProperty(name, props.getProperty(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if NTLM authentication is forced.
|
||||
*
|
||||
* @return <code>true</code> if NTLM authentication is forced.
|
||||
*/
|
||||
public boolean isForceIdentification() {
|
||||
return this.forceIdentification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a flag denoting whether NTLM authentication should be forced.
|
||||
*
|
||||
* @param forceIdentification the force identification flag value to set.
|
||||
*/
|
||||
public void setForceIdentification(boolean forceIdentification) {
|
||||
this.forceIdentification = forceIdentification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a flag denoting whether NTLM should retry whenever authentication
|
||||
* fails. Retry will only occur on an {@link AuthenticationCredentialsNotFoundException}
|
||||
* or {@link InsufficientAuthenticationException}.
|
||||
*
|
||||
* @param retryOnFailure the retry on failure flag value to set.
|
||||
*/
|
||||
public void setRetryOnAuthFailure(boolean retryOnFailure) {
|
||||
this.retryOnAuthFailure = retryOnFailure;
|
||||
}
|
||||
|
||||
//~ Protected Methods ==============================================================================================
|
||||
|
||||
protected void doFilter(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws IOException {
|
||||
final HttpSession session = request.getSession();
|
||||
Integer ntlmState = (Integer) session.getAttribute(STATE_ATTR);
|
||||
|
||||
// Start NTLM negotiations the first time through the filter
|
||||
if (ntlmState == null) {
|
||||
if (forceIdentification) {
|
||||
logger.debug("Starting NTLM handshake");
|
||||
session.setAttribute(STATE_ATTR, BEGIN);
|
||||
throw new NtlmBeginHandshakeException();
|
||||
} else {
|
||||
logger.debug("NTLM handshake not yet started");
|
||||
session.setAttribute(STATE_ATTR, DELAYED);
|
||||
}
|
||||
}
|
||||
|
||||
// IE will send a Type 1 message to reauthenticate the user during an HTTP POST
|
||||
if (ntlmState == COMPLETE && this.reAuthOnIEPost(request))
|
||||
ntlmState = BEGIN;
|
||||
|
||||
final String authMessage = request.getHeader("Authorization");
|
||||
if (ntlmState != COMPLETE && authMessage != null && authMessage.startsWith("NTLM ")) {
|
||||
final UniAddress dcAddress = this.getDCAddress(session);
|
||||
if (ntlmState == BEGIN) {
|
||||
logger.debug("Processing NTLM Type 1 Message");
|
||||
session.setAttribute(STATE_ATTR, NEGOTIATE);
|
||||
this.processType1Message(authMessage, session, dcAddress);
|
||||
} else {
|
||||
logger.debug("Processing NTLM Type 3 Message");
|
||||
final NtlmPasswordAuthentication auth = this.processType3Message(authMessage, session, dcAddress);
|
||||
logger.debug("NTLM negotiation complete");
|
||||
this.logon(session, dcAddress, auth);
|
||||
session.setAttribute(STATE_ATTR, COMPLETE);
|
||||
|
||||
// Do not reauthenticate the user in Acegi during an IE POST
|
||||
final Authentication myCurrentAuth = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (myCurrentAuth == null || myCurrentAuth instanceof AnonymousAuthenticationToken) {
|
||||
logger.debug("Authenticating user credentials");
|
||||
this.authenticate(request, response, session, auth);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//~ Private Methods ================================================================================================
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if reauthentication is needed on an IE POST.
|
||||
*/
|
||||
private boolean reAuthOnIEPost(final HttpServletRequest request) {
|
||||
String ua = request.getHeader("User-Agent");
|
||||
return (request.getMethod().equalsIgnoreCase("POST") && ua != null && ua.indexOf("MSIE") != -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a Type 2 message from the provided Type 1 message.
|
||||
*
|
||||
* @param message the Type 1 message to process.
|
||||
* @param session the <code>HTTPSession</code> object.
|
||||
* @param dcAddress the domain controller address.
|
||||
* @throws IOException
|
||||
*/
|
||||
private void processType1Message(final String message, final HttpSession session, final UniAddress dcAddress) throws IOException {
|
||||
final Type2Message type2msg = new Type2Message(
|
||||
new Type1Message(Base64.decode(message.substring(5))),
|
||||
this.getChallenge(session, dcAddress),
|
||||
null);
|
||||
throw new NtlmType2MessageException(Base64.encode(type2msg.toByteArray()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns an <code>NtlmPasswordAuthentication</code> object
|
||||
* from the provided Type 3 message.
|
||||
*
|
||||
* @param message the Type 3 message to process.
|
||||
* @param session the <code>HTTPSession</code> object.
|
||||
* @param dcAddress the domain controller address.
|
||||
* @return an <code>NtlmPasswordAuthentication</code> object.
|
||||
* @throws IOException
|
||||
*/
|
||||
private NtlmPasswordAuthentication processType3Message(final String message, final HttpSession session, final UniAddress dcAddress) throws IOException {
|
||||
final Type3Message type3msg = new Type3Message(Base64.decode(message.substring(5)));
|
||||
final byte[] lmResponse = (type3msg.getLMResponse() != null) ? type3msg.getLMResponse() : new byte[0];
|
||||
final byte[] ntResponse = (type3msg.getNTResponse() != null) ? type3msg.getNTResponse() : new byte[0];
|
||||
return new NtlmPasswordAuthentication(
|
||||
type3msg.getDomain(),
|
||||
type3msg.getUser(),
|
||||
this.getChallenge(session, dcAddress),
|
||||
lmResponse,
|
||||
ntResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the user credentials against the domain controller.
|
||||
*
|
||||
* @param session the <code>HTTPSession</code> object.
|
||||
* @param dcAddress the domain controller address.
|
||||
* @param auth the <code>NtlmPasswordAuthentication</code> object.
|
||||
* @throws IOException
|
||||
*/
|
||||
private void logon(final HttpSession session, final UniAddress dcAddress, final NtlmPasswordAuthentication auth) throws IOException {
|
||||
try {
|
||||
SmbSession.logon(dcAddress, auth);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(auth + " successfully authenticated against " + dcAddress);
|
||||
}
|
||||
} catch(SmbAuthException e) {
|
||||
logger.error("Credentials " + auth + " were not accepted by the domain controller " + dcAddress);
|
||||
throw new BadCredentialsException("Bad NTLM credentials");
|
||||
} finally {
|
||||
if (loadBalance)
|
||||
session.removeAttribute(CHALLENGE_ATTR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates the user credentials acquired from NTLM against the Acegi
|
||||
* Security <code>AuthenticationManager</code>.
|
||||
*
|
||||
* @param request the <code>HttpServletRequest</code> object.
|
||||
* @param response the <code>HttpServletResponse</code> object.
|
||||
* @param session the <code>HttpSession</code> object.
|
||||
* @param auth the <code>NtlmPasswordAuthentication</code> object.
|
||||
* @throws IOException
|
||||
*/
|
||||
private void authenticate(final HttpServletRequest request, final HttpServletResponse response, final HttpSession session, final NtlmPasswordAuthentication auth) throws IOException {
|
||||
final Authentication authResult;
|
||||
final UsernamePasswordAuthenticationToken authRequest;
|
||||
final Authentication backupAuth;
|
||||
|
||||
authRequest = new NtlmUsernamePasswordAuthenticationToken(auth, stripDomain);
|
||||
authRequest.setDetails(new WebAuthenticationDetails(request));
|
||||
|
||||
// Place the last username attempted into HttpSession for views
|
||||
session.setAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_LAST_USERNAME_KEY, authRequest.getName());
|
||||
|
||||
// Backup the current authentication in case of an AuthenticationException
|
||||
backupAuth = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
try {
|
||||
// Authenitcate the user with the authentication manager
|
||||
authResult = authenticationManager.authenticate(authRequest);
|
||||
} catch (AuthenticationException failed) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Authentication request for user: " + authRequest.getName() + " failed: " + failed.toString());
|
||||
}
|
||||
|
||||
// Reset the backup Authentication object and rethrow the AuthenticationException
|
||||
SecurityContextHolder.getContext().setAuthentication(backupAuth);
|
||||
|
||||
if (retryOnAuthFailure && (failed instanceof AuthenticationCredentialsNotFoundException || failed instanceof InsufficientAuthenticationException)) {
|
||||
logger.debug("Restart NTLM authentication handshake due to AuthenticationException");
|
||||
session.setAttribute(STATE_ATTR, BEGIN);
|
||||
throw new NtlmBeginHandshakeException();
|
||||
}
|
||||
|
||||
throw failed;
|
||||
}
|
||||
|
||||
// Set the Authentication object with the valid authentication result
|
||||
SecurityContextHolder.getContext().setAuthentication(authResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the domain controller address based on the <code>loadBalance</code>
|
||||
* setting.
|
||||
*
|
||||
* @param session the <code>HttpSession</code> object.
|
||||
* @return the domain controller address.
|
||||
* @throws UnknownHostException
|
||||
* @throws SmbException
|
||||
*/
|
||||
private UniAddress getDCAddress(final HttpSession session) throws UnknownHostException, SmbException {
|
||||
if (loadBalance) {
|
||||
NtlmChallenge chal = (NtlmChallenge) session.getAttribute(CHALLENGE_ATTR);
|
||||
if (chal == null) {
|
||||
chal = SmbSession.getChallengeForDomain();
|
||||
session.setAttribute(CHALLENGE_ATTR, chal);
|
||||
}
|
||||
return chal.dc;
|
||||
}
|
||||
|
||||
return UniAddress.getByName(domainController, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the domain controller challenge based on the <code>loadBalance</code>
|
||||
* setting.
|
||||
*
|
||||
* @param session the <code>HttpSession</code> object.
|
||||
* @param dcAddress the domain controller address.
|
||||
* @return the domain controller challenge.
|
||||
* @throws UnknownHostException
|
||||
* @throws SmbException
|
||||
*/
|
||||
private byte[] getChallenge(final HttpSession session, final UniAddress dcAddress) throws UnknownHostException, SmbException {
|
||||
if (loadBalance)
|
||||
return ((NtlmChallenge) session.getAttribute(CHALLENGE_ATTR)).challenge;
|
||||
|
||||
return SmbSession.getChallenge(dcAddress);
|
||||
}
|
||||
|
||||
} // End NtlmProcessingFilter
|
119
ntlm/src/main/java/org/acegisecurity/ui/ntlm/NtlmProcessingFilterEntryPoint.java
Executable file
119
ntlm/src/main/java/org/acegisecurity/ui/ntlm/NtlmProcessingFilterEntryPoint.java
Executable file
|
@ -0,0 +1,119 @@
|
|||
/* Copyright 2004-2007 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.acegisecurity.ui.ntlm;
|
||||
|
||||
import org.acegisecurity.AuthenticationCredentialsNotFoundException;
|
||||
import org.acegisecurity.AuthenticationException;
|
||||
import org.acegisecurity.InsufficientAuthenticationException;
|
||||
import org.acegisecurity.ui.AuthenticationEntryPoint;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import jcifs.Config;
|
||||
|
||||
/**
|
||||
* Used by <code>ExceptionTranslationFilter</code> to assist with the NTLM
|
||||
* negotiation. Also handles redirecting the user to the authentication
|
||||
* failure URL if an {@link AuthenticationException} that is not a subclass of
|
||||
* {@link NtlmBaseException} is received.
|
||||
*
|
||||
* @author Davide Baroncelli
|
||||
* @author Edward Smith
|
||||
* @version $Id$
|
||||
*/
|
||||
public class NtlmProcessingFilterEntryPoint implements AuthenticationEntryPoint, InitializingBean {
|
||||
//~ Static fields/initializers =============================================
|
||||
|
||||
private static final Log logger = LogFactory.getLog(NtlmProcessingFilterEntryPoint.class);
|
||||
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
/** Where to redirect the browser to if authentication fails */
|
||||
private String authenticationFailureUrl;
|
||||
|
||||
//~ Methods ================================================================
|
||||
|
||||
/**
|
||||
* Ensures an authentication failure URL has been provided in the bean
|
||||
* configuration file.
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.hasLength(authenticationFailureUrl, "authenticationFailureUrl must be specified");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the authentication failure URL.
|
||||
*
|
||||
* @param authenticationFailureUrl the authentication failure URL.
|
||||
*/
|
||||
public void setAuthenticationFailureUrl(String authenticationFailureUrl) {
|
||||
this.authenticationFailureUrl = authenticationFailureUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an NTLM challenge to the browser requiring authentication. The
|
||||
* WWW-Authenticate header is populated with the appropriate information
|
||||
* during the negotiation lifecycle by calling the getMessage() method
|
||||
* from an NTLM-specific subclass of {@link NtlmBaseException}:
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>{@link NtlmBeginHandshakeException}: NTLM
|
||||
* <li>{@link NtlmType2MessageException}: NTLM <base64-encoded type-2-message>
|
||||
* </ul>
|
||||
*
|
||||
* If the {@link AuthenticationException} is not a subclass of
|
||||
* {@link NtlmBaseException}, then redirect the user to the authentication
|
||||
* failure URL.
|
||||
*
|
||||
* @param request The {@link HttpServletRequest} object.
|
||||
* @param response Then {@link HttpServletResponse} object.
|
||||
* @param authException Either {@link NtlmBeginHandshakeException},
|
||||
* {@link NtlmType2MessageException}, or
|
||||
* {@link AuthenticationException}
|
||||
*/
|
||||
public void commence(final ServletRequest request, final ServletResponse response, final AuthenticationException authException) throws IOException, ServletException {
|
||||
final HttpServletResponse resp = (HttpServletResponse) response;
|
||||
|
||||
if (authException instanceof NtlmBaseException) {
|
||||
if (authException instanceof NtlmType2MessageException) {
|
||||
((NtlmType2MessageException) authException).preserveAuthentication();
|
||||
}
|
||||
resp.setHeader("WWW-Authenticate", authException.getMessage());
|
||||
resp.setHeader("Connection", "Keep-Alive");
|
||||
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
resp.setContentLength(0);
|
||||
resp.flushBuffer();
|
||||
} else {
|
||||
String url = authenticationFailureUrl;
|
||||
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
||||
url = ((HttpServletRequest) request).getContextPath() + url;
|
||||
}
|
||||
|
||||
resp.sendRedirect(resp.encodeRedirectURL(url));
|
||||
}
|
||||
}
|
||||
|
||||
} // End NtlmProcessingFilterEntryPoint
|
|
@ -0,0 +1,48 @@
|
|||
/* Copyright 2004-2007 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.acegisecurity.ui.ntlm;
|
||||
|
||||
import org.acegisecurity.Authentication;
|
||||
import org.acegisecurity.AuthenticationException;
|
||||
import org.acegisecurity.context.SecurityContextHolder;
|
||||
|
||||
/**
|
||||
* Contains the NTLM Type 2 message that is sent back to the client during
|
||||
* negotiations.
|
||||
*
|
||||
* @author Edward Smith
|
||||
*/
|
||||
public class NtlmType2MessageException extends NtlmBaseException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Authentication auth;
|
||||
|
||||
public NtlmType2MessageException(final String type2Msg) {
|
||||
super("NTLM " + type2Msg);
|
||||
auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
}
|
||||
|
||||
/**
|
||||
* Preserve the existing <code>Authentication</code> object each time
|
||||
* Internet Explorer does a POST.
|
||||
*/
|
||||
public void preserveAuthentication() {
|
||||
if (auth != null)
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
}
|
||||
|
||||
} // End NTLMType2MessageException
|
|
@ -0,0 +1,49 @@
|
|||
/* Copyright 2004-2007 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.acegisecurity.ui.ntlm;
|
||||
|
||||
import jcifs.smb.NtlmPasswordAuthentication;
|
||||
|
||||
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
|
||||
|
||||
/**
|
||||
* An NTLM-specific {@link UsernamePasswordAuthenticationToken} that allows
|
||||
* any provider to bypass the problem of an empty password since NTLM does
|
||||
* not retrieve the user's password from the PDC.
|
||||
*
|
||||
* @author Sylvain Mougenot
|
||||
*/
|
||||
public class NtlmUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ACEGI often checks password ; but we do not have one. This is the replacement password
|
||||
*/
|
||||
public static final String DEFAULT_PASSWORD = "";
|
||||
|
||||
/**
|
||||
* Create an NTLM {@link UsernamePasswordAuthenticationToken} using the
|
||||
* JCIFS {@link NtlmPasswordAuthentication} object.
|
||||
*
|
||||
* @param ntlmAuth The {@link NtlmPasswordAuthentication} object.
|
||||
* @param stripDomain Uses just the username if <code>true</code>,
|
||||
* otherwise use the username and domain name.
|
||||
*/
|
||||
public NtlmUsernamePasswordAuthenticationToken(final NtlmPasswordAuthentication ntlmAuth, final boolean stripDomain) {
|
||||
super((stripDomain) ? ntlmAuth.getUsername() : ntlmAuth.getName(), DEFAULT_PASSWORD);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package org.acegisecurity.ui.ntlm.ldap.authenticator;
|
||||
|
||||
import org.acegisecurity.*;
|
||||
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
|
||||
import org.acegisecurity.providers.ldap.LdapAuthenticationProvider;
|
||||
import org.acegisecurity.providers.ldap.LdapAuthoritiesPopulator;
|
||||
import org.acegisecurity.ui.ntlm.NtlmUsernamePasswordAuthenticationToken;
|
||||
import org.acegisecurity.userdetails.UserDetails;
|
||||
import org.acegisecurity.userdetails.ldap.LdapUserDetails;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
|
||||
/**
|
||||
* This provider implements specialized behaviour if the supplied {@link Authentication} object is
|
||||
* from NTLM. In other cases calls the parent implementation.
|
||||
*
|
||||
* @author sylvain.mougenot
|
||||
*
|
||||
*/
|
||||
public class NtlmAwareLdapAuthenticationProvider extends LdapAuthenticationProvider {
|
||||
private static final Log logger = LogFactory.getLog(NtlmAwareLdapAuthenticationProvider.class);
|
||||
|
||||
/**
|
||||
* NTLM aware authenticator
|
||||
*/
|
||||
private NtlmAwareLdapAuthenticator authenticator;
|
||||
|
||||
/**
|
||||
* @param authenticator
|
||||
* @param authoritiesPopulator
|
||||
*/
|
||||
public NtlmAwareLdapAuthenticationProvider(NtlmAwareLdapAuthenticator authenticator,
|
||||
LdapAuthoritiesPopulator authoritiesPopulator) {
|
||||
super(authenticator, authoritiesPopulator);
|
||||
this.authenticator = authenticator;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.acegisecurity.providers.ldap.LdapAuthenticationProvider#retrieveUser(java.lang.String,
|
||||
* org.acegisecurity.providers.UsernamePasswordAuthenticationToken)
|
||||
*/
|
||||
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
|
||||
throws AuthenticationException {
|
||||
final UserDetails myDetails;
|
||||
|
||||
if (authentication instanceof NtlmUsernamePasswordAuthenticationToken) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Ntlm Token for Authentication"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
// Only loads LDAP data
|
||||
myDetails = retrieveUser(username, (NtlmUsernamePasswordAuthenticationToken) authentication);
|
||||
} else {
|
||||
// calls parent implementation
|
||||
myDetails = super.retrieveUser(username, authentication);
|
||||
}
|
||||
|
||||
return myDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication has already been done. We need a particular behviour
|
||||
* because the parent check password consistency. But we do not have the
|
||||
* password (even if the user is authenticated).
|
||||
*
|
||||
* @see NtlmUsernamePasswordAuthenticationToken#DEFAULT_PASSWORD
|
||||
* @param username
|
||||
* @param authentication
|
||||
* @return
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
protected UserDetails retrieveUser(String username, NtlmUsernamePasswordAuthenticationToken authentication)
|
||||
throws AuthenticationException {
|
||||
// identifiant obligatoire
|
||||
if (!StringUtils.hasLength(username)) {
|
||||
throw new BadCredentialsException(messages.getMessage(
|
||||
"LdapAuthenticationProvider.emptyUsername",
|
||||
"Empty Username"));
|
||||
}
|
||||
|
||||
// NB: password is just the default value
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Retrieving user " + username);
|
||||
}
|
||||
|
||||
try {
|
||||
// Complies with our lack of password (can't bind)
|
||||
DirContextOperations ldapUser = authenticator.authenticate(authentication);
|
||||
|
||||
GrantedAuthority[] extraAuthorities = getAuthoritiesPopulator().getGrantedAuthorities(ldapUser, username);
|
||||
|
||||
return getUserDetailsContextMapper().mapUserFromContext(ldapUser, username, extraAuthorities);
|
||||
|
||||
} catch (DataAccessException ldapAccessFailure) {
|
||||
throw new AuthenticationServiceException(ldapAccessFailure
|
||||
.getMessage(), ldapAccessFailure);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package org.acegisecurity.ui.ntlm.ldap.authenticator;
|
||||
|
||||
import org.acegisecurity.providers.ldap.LdapAuthenticator;
|
||||
import org.acegisecurity.ui.ntlm.NtlmUsernamePasswordAuthenticationToken;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
|
||||
/**
|
||||
* Authenticator compliant with NTLM part done previously (for authentication).
|
||||
*
|
||||
* @author sylvain.mougenot
|
||||
*
|
||||
*/
|
||||
public interface NtlmAwareLdapAuthenticator extends LdapAuthenticator {
|
||||
/**
|
||||
* Authentication was done previously by NTLM.
|
||||
* Obtains additional user informations from the directory.
|
||||
*
|
||||
* @param aUserToken Ntlm issued authentication Token
|
||||
* @return the details of the successfully authenticated user.
|
||||
*/
|
||||
DirContextOperations authenticate(NtlmUsernamePasswordAuthenticationToken aUserToken);
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package org.acegisecurity.ui.ntlm.ldap.authenticator;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.acegisecurity.BadCredentialsException;
|
||||
import org.acegisecurity.Authentication;
|
||||
import org.acegisecurity.ldap.InitialDirContextFactory;
|
||||
import org.acegisecurity.ldap.SpringSecurityLdapTemplate;
|
||||
import org.acegisecurity.providers.ldap.authenticator.BindAuthenticator;
|
||||
import org.acegisecurity.ui.ntlm.NtlmUsernamePasswordAuthenticationToken;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
|
||||
/**
|
||||
* Fullfill the User details after NTLM authentication was done. Or (if no NTLM
|
||||
* authentication done) act as the parent to authenticate the user
|
||||
*
|
||||
* @author sylvain.mougenot
|
||||
*
|
||||
*/
|
||||
public class NtlmAwareLdapAuthenticatorImpl extends BindAuthenticator {
|
||||
/**
|
||||
* Logger for this class
|
||||
*/
|
||||
private static final Log logger = LogFactory.getLog(NtlmAwareLdapAuthenticatorImpl.class);
|
||||
|
||||
/**
|
||||
* @param initialDirContextFactory
|
||||
*/
|
||||
public NtlmAwareLdapAuthenticatorImpl(InitialDirContextFactory initialDirContextFactory) {
|
||||
super(initialDirContextFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the template without bind requirements.
|
||||
*
|
||||
* @param aUserDn
|
||||
* @param aUserName
|
||||
* @see #loadDetail(SpringSecurityLdapTemplate, String, String)
|
||||
* @return
|
||||
*/
|
||||
protected DirContextOperations bindWithoutDn(String aUserDn, String aUserName) {
|
||||
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(getInitialDirContextFactory());
|
||||
return loadDetail(template, aUserDn, aUserName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load datas
|
||||
*
|
||||
* @param aTemplate
|
||||
* @param aUserDn
|
||||
* @param aUserName
|
||||
* @return
|
||||
*/
|
||||
protected DirContextOperations loadDetail(SpringSecurityLdapTemplate aTemplate, String aUserDn, String aUserName) {
|
||||
try {
|
||||
DirContextOperations user = aTemplate.retrieveEntry(aUserDn, getUserAttributes());
|
||||
|
||||
return user;
|
||||
} catch (BadCredentialsException e) {
|
||||
// This will be thrown if an invalid user name is used and the
|
||||
// method may
|
||||
// be called multiple times to try different names, so we trap the
|
||||
// exception
|
||||
// unless a subclass wishes to implement more specialized behaviour.
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to bind as " + aUserDn + ": "
|
||||
+ e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.acegisecurity.ui.ntlm.NtlmAwareLdapAuthenticator#authenticate(org.acegisecurity.ui.ntlm.NtlmUsernamePasswordAuthenticationToken)
|
||||
*/
|
||||
public DirContextOperations authenticate(Authentication authentication) {
|
||||
if (!(authentication instanceof NtlmUsernamePasswordAuthenticationToken)) {
|
||||
return super.authenticate(authentication);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("authenticate(NtlmUsernamePasswordAuthenticationToken) - start"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
final String userName = authentication.getName();
|
||||
DirContextOperations user = null;
|
||||
|
||||
// If DN patterns are configured, try authenticating with them directly
|
||||
Iterator myDns = getUserDns(userName).iterator();
|
||||
|
||||
// tries them all until we found something
|
||||
while (myDns.hasNext() && (user == null)) {
|
||||
user = bindWithoutDn((String) myDns.next(), userName);
|
||||
}
|
||||
|
||||
// Otherwise use the configured locator to find the user
|
||||
// and authenticate with the returned DN.
|
||||
if ((user == null) && (getUserSearch() != null)) {
|
||||
DirContextOperations userFromSearch = getUserSearch().searchForUser(userName);
|
||||
// lancer l'identificvation
|
||||
user = bindWithoutDn(userFromSearch.getDn().toString(), userName);
|
||||
}
|
||||
|
||||
// Failed to locate the user in the LDAP directory
|
||||
if (user == null) {
|
||||
throw new BadCredentialsException(messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("authenticate(NtlmUsernamePasswordAuthenticationToken) - end"); //$NON-NLS-1$
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
|
||||
<display-name>Acegi NTLM</display-name>
|
||||
|
||||
<!-- 1. Setup two parameters: -->
|
||||
<!-- a) Acegi's configuration file -->
|
||||
<!-- b) Loggin configuration file -->
|
||||
<context-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>/WEB-INF/applicationContext.xml</param-value>
|
||||
</context-param>
|
||||
|
||||
<context-param>
|
||||
<param-name>log4jConfigLocation</param-name>
|
||||
<param-value>/WEB-INF/log4j.properties</param-value>
|
||||
</context-param>
|
||||
|
||||
<!-- 2. Setup the Acegi Filter Chain Proxy -->
|
||||
<filter>
|
||||
<filter-name>Acegi Filter Chain Proxy</filter-name>
|
||||
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
|
||||
<init-param>
|
||||
<param-name>targetClass</param-name>
|
||||
<param-value>org.acegisecurity.util.FilterChainProxy</param-value>
|
||||
</init-param>
|
||||
</filter>
|
||||
|
||||
<filter-mapping>
|
||||
<filter-name>Acegi Filter Chain Proxy</filter-name>
|
||||
<url-pattern>/**</url-pattern>
|
||||
</filter-mapping>
|
||||
|
||||
<!-- 3. Setup three listeners -->
|
||||
<!-- a) Setup a listener to connect spring with the web context -->
|
||||
<listener>
|
||||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||
</listener>
|
||||
|
||||
<!-- b) Setup a listener to connect spring with log4J -->
|
||||
<listener>
|
||||
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
|
||||
</listener>
|
||||
|
||||
<!-- c) Setup ACEGI to subscribe to http session events in the web context -->
|
||||
<listener>
|
||||
<listener-class>org.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>
|
||||
</listener>
|
||||
|
||||
<welcome-file-list>
|
||||
<welcome-file>index.html</welcome-file>
|
||||
<welcome-file>index.htm</welcome-file>
|
||||
<welcome-file>index.jsp</welcome-file>
|
||||
<welcome-file>default.html</welcome-file>
|
||||
<welcome-file>default.htm</welcome-file>
|
||||
<welcome-file>default.jsp</welcome-file>
|
||||
</welcome-file-list>
|
||||
</web-app>
|
Loading…
Reference in New Issue