14 changed files with 1329 additions and 0 deletions
@ -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
|
||||||
@ -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