Browse Source

SEC-319: Improvements to Siteminder integration: Create its own authentication provider & reeval strategy. Note that documentation not yet complete, but code is functional, test-covered and validated in a Siteminder environment.

Scott McCrory 19 years ago
parent
commit
8d3a2b42d9

+ 132 - 0
core/src/main/java/org/acegisecurity/providers/siteminder/SiteminderAuthenticationProvider.java

@@ -0,0 +1,132 @@
+/* Copyright 2004, 2005, 2006 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.providers.siteminder;
+
+import org.acegisecurity.AccountExpiredException;
+import org.acegisecurity.AuthenticationException;
+import org.acegisecurity.AuthenticationServiceException;
+import org.acegisecurity.CredentialsExpiredException;
+import org.acegisecurity.DisabledException;
+import org.acegisecurity.LockedException;
+import org.acegisecurity.providers.AuthenticationProvider;
+import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+import org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider;
+import org.acegisecurity.userdetails.UserDetails;
+import org.acegisecurity.userdetails.UserDetailsService;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.dao.DataAccessException;
+import org.springframework.util.Assert;
+
+/**
+ * An {@link AuthenticationProvider} implementation that retrieves user details from an {@link UserDetailsService}.
+ *
+ * @author Scott McCrory
+ * @version $Id: SiteminderAuthenticationProvider.java 1582 2006-07-15 15:18:51Z smccrory $
+ */
+public class SiteminderAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
+
+    /**
+     * Our logging object
+     */
+    private static final Log logger = LogFactory.getLog(SiteminderAuthenticationProvider.class);
+
+    //~ Instance fields ================================================================================================
+
+    /**
+     * Our user details service (which does the real work of checking the user against a back-end user store).
+     */
+    private UserDetailsService userDetailsService;
+
+    //~ Methods ========================================================================================================
+
+    /**
+     * @see org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider#additionalAuthenticationChecks(org.acegisecurity.userdetails.UserDetails, org.acegisecurity.providers.UsernamePasswordAuthenticationToken)
+     */
+    protected void additionalAuthenticationChecks(final UserDetails user,
+            final UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
+
+        // No need for password authentication checks - we only expect one identifying string
+        // from the HTTP Request header (as populated by Siteminder), but we do need to see if
+        // the user's account is OK to let them in.
+        if (!user.isEnabled()) {
+            throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled",
+                    "Account disabled"));
+        }
+
+        if (!user.isAccountNonExpired()) {
+            throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired",
+                    "Account expired"));
+        }
+
+        if (!user.isAccountNonLocked()) {
+            throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked",
+                    "Account locked"));
+        }
+
+        if (!user.isCredentialsNonExpired()) {
+            throw new CredentialsExpiredException(messages.getMessage(
+                    "AbstractUserDetailsAuthenticationProvider.credentialsExpired", "Credentials expired"));
+        }
+
+    }
+
+    /**
+     * @see org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider#doAfterPropertiesSet()
+     */
+    protected void doAfterPropertiesSet() throws Exception {
+        Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
+    }
+
+    /**
+     * Return the user details service.
+     * @return The user details service.
+     */
+    public UserDetailsService getUserDetailsService() {
+        return userDetailsService;
+    }
+
+    /**
+     * @see org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider#retrieveUser(java.lang.String, org.acegisecurity.providers.UsernamePasswordAuthenticationToken)
+     */
+    protected final UserDetails retrieveUser(final String username,
+            final UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
+
+        UserDetails loadedUser;
+
+        try {
+            loadedUser = this.getUserDetailsService().loadUserByUsername(username);
+        } catch (DataAccessException repositoryProblem) {
+            throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);
+        }
+
+        if (loadedUser == null) {
+            throw new AuthenticationServiceException(
+                    "UserDetailsService returned null, which is an interface contract violation");
+        }
+
+        return loadedUser;
+    }
+
+    /**
+     * Sets the user details service.
+     * @param userDetailsService The user details service.
+     */
+    public void setUserDetailsService(final UserDetailsService userDetailsService) {
+        this.userDetailsService = userDetailsService;
+    }
+
+}

+ 5 - 0
core/src/main/java/org/acegisecurity/providers/siteminder/package.html

@@ -0,0 +1,5 @@
+<html>
+<body>
+A Siteminder authentication provider.
+</body>
+</html>

+ 34 - 88
core/src/main/java/org/acegisecurity/ui/webapp/SiteminderAuthenticationProcessingFilter.java

@@ -15,36 +15,40 @@
 
 
 package org.acegisecurity.ui.webapp;
 package org.acegisecurity.ui.webapp;
 
 
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
 import org.acegisecurity.Authentication;
 import org.acegisecurity.Authentication;
 import org.acegisecurity.AuthenticationException;
 import org.acegisecurity.AuthenticationException;
-
 import org.acegisecurity.context.HttpSessionContextIntegrationFilter;
 import org.acegisecurity.context.HttpSessionContextIntegrationFilter;
 import org.acegisecurity.context.SecurityContext;
 import org.acegisecurity.context.SecurityContext;
-
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
-
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.commons.logging.LogFactory;
 
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-
 /**
 /**
- * Extends Acegi's AuthenticationProcessingFilter to pick up CA/Netegrity Siteminder headers.<P>Also provides a
- * backup form-based authentication and the ability set source key names.</p>
- *  <P><B>Siteminder</B> must present two <B>headers</B> to this filter, a username and password. You must set the
+ * Extends Acegi's AuthenticationProcessingFilter to pick up CA/Netegrity Siteminder headers.
+ * 
+ * <P>Also provides a backup form-based authentication and the ability set source key names.</p>
+ * 
+ * <P><B>Siteminder</B> must present two <B>headers</B> to this filter, a username and password. You must set the
  * header keys before this filter is used for authentication, otherwise Siteminder checks will be skipped. If the
  * header keys before this filter is used for authentication, otherwise Siteminder checks will be skipped. If the
  * Siteminder check is unsuccessful (i.e. if the headers are not found), then the form parameters will be checked (see
  * Siteminder check is unsuccessful (i.e. if the headers are not found), then the form parameters will be checked (see
  * next paragraph). This allows applications to optionally function even when their Siteminder infrastructure is
  * next paragraph). This allows applications to optionally function even when their Siteminder infrastructure is
  * unavailable, as is often the case during development.</p>
  * unavailable, as is often the case during development.</p>
- *  <P><B>Login forms</B> must present two <B>parameters</B> to this filter: a username and password. If not
+ * 
+ * <P><B>Login forms</B> must present two <B>parameters</B> to this filter: a username and password. If not
  * specified, the parameter names to use are contained in the static fields {@link #ACEGI_SECURITY_FORM_USERNAME_KEY}
  * specified, the parameter names to use are contained in the static fields {@link #ACEGI_SECURITY_FORM_USERNAME_KEY}
  * and {@link #ACEGI_SECURITY_FORM_PASSWORD_KEY}.</p>
  * and {@link #ACEGI_SECURITY_FORM_PASSWORD_KEY}.</p>
- *  <P><B>Do not use this class directly.</B> Instead, configure <code>web.xml</code> to use the {@link
+ * 
+ * <P><B>Do not use this class directly.</B> Instead, configure <code>web.xml</code> to use the {@link
  * org.acegisecurity.util.FilterToBeanProxy}.</p>
  * org.acegisecurity.util.FilterToBeanProxy}.</p>
+ * 
+ * @author Scott McCrory
+ * @version $Id$
  */
  */
 public class SiteminderAuthenticationProcessingFilter extends AuthenticationProcessingFilter {
 public class SiteminderAuthenticationProcessingFilter extends AuthenticationProcessingFilter {
+
     //~ Static fields/initializers =====================================================================================
     //~ Static fields/initializers =====================================================================================
 
 
     /** Log instance for debugging */
     /** Log instance for debugging */
@@ -52,21 +56,15 @@ public class SiteminderAuthenticationProcessingFilter extends AuthenticationProc
 
 
     //~ Instance fields ================================================================================================
     //~ Instance fields ================================================================================================
 
 
-    /** Form password request key. */
-    private String formPasswordParameterKey = null;
-
     /** Form username request key. */
     /** Form username request key. */
     private String formUsernameParameterKey = null;
     private String formUsernameParameterKey = null;
 
 
-    /** Siteminder password header key. */
-    private String siteminderPasswordHeaderKey = null;
-
     /** Siteminder username header key. */
     /** Siteminder username header key. */
     private String siteminderUsernameHeaderKey = null;
     private String siteminderUsernameHeaderKey = null;
 
 
     //~ Constructors ===================================================================================================
     //~ Constructors ===================================================================================================
 
 
-/**
+    /**
      * Basic constructor.
      * Basic constructor.
      */
      */
     public SiteminderAuthenticationProcessingFilter() {
     public SiteminderAuthenticationProcessingFilter() {
@@ -76,24 +74,19 @@ public class SiteminderAuthenticationProcessingFilter extends AuthenticationProc
     //~ Methods ========================================================================================================
     //~ Methods ========================================================================================================
 
 
     /**
     /**
-     * 
      * @see org.acegisecurity.ui.AbstractProcessingFilter#attemptAuthentication(javax.servlet.http.HttpServletRequest)
      * @see org.acegisecurity.ui.AbstractProcessingFilter#attemptAuthentication(javax.servlet.http.HttpServletRequest)
      */
      */
-    public Authentication attemptAuthentication(HttpServletRequest request)
-        throws AuthenticationException {
+    public Authentication attemptAuthentication(final HttpServletRequest request) throws AuthenticationException {
+
         String username = null;
         String username = null;
-        String password = null;
 
 
-        // Check the Siteminder headers for authentication info
-        if ((siteminderUsernameHeaderKey != null) && (siteminderUsernameHeaderKey.length() > 0)
-            && (siteminderPasswordHeaderKey != null) && (siteminderPasswordHeaderKey.length() > 0)) {
+        // Check the Siteminder header for identification info
+        if ((siteminderUsernameHeaderKey != null) && (siteminderUsernameHeaderKey.length() > 0)) {
             username = request.getHeader(siteminderUsernameHeaderKey);
             username = request.getHeader(siteminderUsernameHeaderKey);
-            password = request.getHeader(siteminderPasswordHeaderKey);
         }
         }
 
 
-        // If the Siteminder authentication info wasn't available, then get it
-        // from the form parameters
-        if ((username == null) || (username.length() == 0) || (password == null) || (password.length() == 0)) {
+        // If the Siteminder identification info wasn't available, then try to get it from the form
+        if ((username == null) || (username.length() == 0)) {
             if (logger.isDebugEnabled()) {
             if (logger.isDebugEnabled()) {
                 logger.debug("Siteminder headers not found for authentication, so trying to use form values");
                 logger.debug("Siteminder headers not found for authentication, so trying to use form values");
             }
             }
@@ -104,7 +97,6 @@ public class SiteminderAuthenticationProcessingFilter extends AuthenticationProc
                 username = request.getParameter(ACEGI_SECURITY_FORM_USERNAME_KEY);
                 username = request.getParameter(ACEGI_SECURITY_FORM_USERNAME_KEY);
             }
             }
 
 
-            password = obtainPassword(request);
         }
         }
 
 
         // Convert username and password to upper case. This is normally not a
         // Convert username and password to upper case. This is normally not a
@@ -117,14 +109,9 @@ public class SiteminderAuthenticationProcessingFilter extends AuthenticationProc
             username = "";
             username = "";
         }
         }
 
 
-        if (password != null) {
-            password = password.toUpperCase();
-        } else {
-            // If password is null, set to blank to avoid a NPE.
-            password = "";
-        }
-
-        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
+        // Pass in a null password value because it isn't relevant for Siteminder.
+        // Of course the AuthenticationManager needs to not care!
+        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, null);
 
 
         // Allow subclasses to set the "details" property
         // Allow subclasses to set the "details" property
         setDetails(request, authRequest);
         setDetails(request, authRequest);
@@ -135,15 +122,6 @@ public class SiteminderAuthenticationProcessingFilter extends AuthenticationProc
         return this.getAuthenticationManager().authenticate(authRequest);
         return this.getAuthenticationManager().authenticate(authRequest);
     }
     }
 
 
-    /**
-     * Returns the form password parameter key.
-     *
-     * @return The form password parameter key.
-     */
-    public String getFormPasswordParameterKey() {
-        return formPasswordParameterKey;
-    }
-
     /**
     /**
      * Returns the form username parameter key.
      * Returns the form username parameter key.
      *
      *
@@ -153,15 +131,6 @@ public class SiteminderAuthenticationProcessingFilter extends AuthenticationProc
         return formUsernameParameterKey;
         return formUsernameParameterKey;
     }
     }
 
 
-    /**
-     * Returns the Siteminder password header key.
-     *
-     * @return The Siteminder password header key.
-     */
-    public String getSiteminderPasswordHeaderKey() {
-        return siteminderPasswordHeaderKey;
-    }
-
     /**
     /**
      * Returns the Siteminder username header key.
      * Returns the Siteminder username header key.
      *
      *
@@ -172,20 +141,14 @@ public class SiteminderAuthenticationProcessingFilter extends AuthenticationProc
     }
     }
 
 
     /**
     /**
-     * Overridden method to obtain different value depending on whether Siteminder or form validation is being
-     * performed.
+     * Overridden method to always return a null (Siteminder doesn't pass on the password).
      *
      *
      * @param request so that request attributes can be retrieved
      * @param request so that request attributes can be retrieved
-     *
      * @return the password that will be presented in the <code>Authentication</code> request token to the
      * @return the password that will be presented in the <code>Authentication</code> request token to the
-     *         <code>AuthenticationManager</code>
+     *         <code>AuthenticationManager</code> (null).
      */
      */
-    protected String obtainPassword(HttpServletRequest request) {
-        if ((formPasswordParameterKey != null) && (formPasswordParameterKey.length() > 0)) {
-            return request.getParameter(formPasswordParameterKey);
-        } else {
-            return request.getParameter(ACEGI_SECURITY_FORM_PASSWORD_KEY);
-        }
+    protected String obtainPassword(final HttpServletRequest request) {
+        return null;
     }
     }
 
 
     /**
     /**
@@ -197,6 +160,7 @@ public class SiteminderAuthenticationProcessingFilter extends AuthenticationProc
      *      javax.servlet.http.HttpServletResponse)
      *      javax.servlet.http.HttpServletResponse)
      */
      */
     protected boolean requiresAuthentication(final HttpServletRequest request, final HttpServletResponse response) {
     protected boolean requiresAuthentication(final HttpServletRequest request, final HttpServletResponse response) {
+
         String uri = request.getRequestURI();
         String uri = request.getRequestURI();
         int pathParamIndex = uri.indexOf(';');
         int pathParamIndex = uri.indexOf(';');
 
 
@@ -208,8 +172,8 @@ public class SiteminderAuthenticationProcessingFilter extends AuthenticationProc
         //attempt authentication if j_secuity_check is present or if the getDefaultTargetUrl() 
         //attempt authentication if j_secuity_check is present or if the getDefaultTargetUrl() 
         //is present and user is not already authenticated. 
         //is present and user is not already authenticated. 
         boolean bAuthenticated = false;
         boolean bAuthenticated = false;
-        SecurityContext context = (SecurityContext) request.getSession()
-                                                           .getAttribute(HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY);
+        SecurityContext context = (SecurityContext) request.getSession().getAttribute(
+                HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY);
 
 
         if (context != null) {
         if (context != null) {
             Authentication auth = context.getAuthentication();
             Authentication auth = context.getAuthentication();
@@ -222,7 +186,7 @@ public class SiteminderAuthenticationProcessingFilter extends AuthenticationProc
 
 
         // if true is returned then authentication will be attempted.
         // if true is returned then authentication will be attempted.
         boolean bAttemptAuthentication = (uri.endsWith(request.getContextPath() + getFilterProcessesUrl()))
         boolean bAttemptAuthentication = (uri.endsWith(request.getContextPath() + getFilterProcessesUrl()))
-            || ((getDefaultTargetUrl() != null) && uri.endsWith(getDefaultTargetUrl()) && !bAuthenticated);
+                || ((getDefaultTargetUrl() != null) && uri.endsWith(getDefaultTargetUrl()) && !bAuthenticated);
 
 
         if (logger.isDebugEnabled()) {
         if (logger.isDebugEnabled()) {
             logger.debug("Authentication attempted for the following URI ==> " + uri + " is " + bAttemptAuthentication);
             logger.debug("Authentication attempted for the following URI ==> " + uri + " is " + bAttemptAuthentication);
@@ -231,15 +195,6 @@ public class SiteminderAuthenticationProcessingFilter extends AuthenticationProc
         return bAttemptAuthentication;
         return bAttemptAuthentication;
     }
     }
 
 
-    /**
-     * Sets the form password parameter key.
-     *
-     * @param key The form password parameter key.
-     */
-    public void setFormPasswordParameterKey(final String key) {
-        this.formPasswordParameterKey = key;
-    }
-
     /**
     /**
      * Sets the form username parameter key.
      * Sets the form username parameter key.
      *
      *
@@ -249,15 +204,6 @@ public class SiteminderAuthenticationProcessingFilter extends AuthenticationProc
         this.formUsernameParameterKey = key;
         this.formUsernameParameterKey = key;
     }
     }
 
 
-    /**
-     * Sets the Siteminder password header key.
-     *
-     * @param key The Siteminder password header key.
-     */
-    public void setSiteminderPasswordHeaderKey(final String key) {
-        this.siteminderPasswordHeaderKey = key;
-    }
-
     /**
     /**
      * Sets the Siteminder username header key.
      * Sets the Siteminder username header key.
      *
      *

+ 1 - 1
core/src/main/java/org/acegisecurity/ui/webapp/package.html

@@ -1,5 +1,5 @@
 <html>
 <html>
 <body>
 <body>
-Authenticates users via a standard web form and <code>HttpSession</code>.
+Authenticates users via HTTP properties, headers and session.
 </body>
 </body>
 </html>
 </html>

+ 430 - 0
core/src/test/java/org/acegisecurity/providers/siteminder/SiteminderAuthenticationProviderTests.java

@@ -0,0 +1,430 @@
+/* Copyright 2004, 2005, 2006 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.providers.siteminder;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.acegisecurity.AccountExpiredException;
+import org.acegisecurity.Authentication;
+import org.acegisecurity.AuthenticationServiceException;
+import org.acegisecurity.BadCredentialsException;
+import org.acegisecurity.CredentialsExpiredException;
+import org.acegisecurity.DisabledException;
+import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.GrantedAuthorityImpl;
+import org.acegisecurity.LockedException;
+import org.acegisecurity.providers.TestingAuthenticationToken;
+import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+import org.acegisecurity.providers.dao.UserCache;
+import org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache;
+import org.acegisecurity.providers.dao.cache.NullUserCache;
+import org.acegisecurity.userdetails.User;
+import org.acegisecurity.userdetails.UserDetails;
+import org.acegisecurity.userdetails.UserDetailsService;
+import org.acegisecurity.userdetails.UsernameNotFoundException;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.DataRetrievalFailureException;
+
+/**
+ * Tests {@link SiteminderAuthenticationProvider}.
+ *
+ * @author Ben Alex
+ * @version $Id: SiteminderAuthenticationProviderTests.java 1582 2006-07-15 15:18:51Z smccrory $
+ */
+public class SiteminderAuthenticationProviderTests extends TestCase {
+    //~ Methods ========================================================================================================
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(SiteminderAuthenticationProviderTests.class);
+    }
+
+    public final void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public void testAuthenticateFailsIfAccountExpired() {
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("peter", "opal");
+
+        SiteminderAuthenticationProvider provider = new SiteminderAuthenticationProvider();
+        provider.setUserDetailsService(new MockUserDetailsServiceUserPeterAccountExpired());
+        provider.setUserCache(new MockUserCache());
+
+        try {
+            provider.authenticate(token);
+            fail("Should have thrown AccountExpiredException");
+        } catch (AccountExpiredException expected) {
+            assertTrue(true);
+        }
+    }
+
+    public void testAuthenticateFailsIfAccountLocked() {
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("peter", "opal");
+
+        SiteminderAuthenticationProvider provider = new SiteminderAuthenticationProvider();
+        provider.setUserDetailsService(new MockUserDetailsServiceUserPeterAccountLocked());
+        provider.setUserCache(new MockUserCache());
+
+        try {
+            provider.authenticate(token);
+            fail("Should have thrown LockedException");
+        } catch (LockedException expected) {
+            assertTrue(true);
+        }
+    }
+
+    public void testAuthenticateFailsIfCredentialsExpired() {
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("peter", "opal");
+
+        SiteminderAuthenticationProvider provider = new SiteminderAuthenticationProvider();
+        provider.setUserDetailsService(new MockUserDetailsServiceUserPeterCredentialsExpired());
+        provider.setUserCache(new MockUserCache());
+
+        try {
+            provider.authenticate(token);
+            fail("Should have thrown CredentialsExpiredException");
+        } catch (CredentialsExpiredException expected) {
+            assertTrue(true);
+        }
+
+    }
+
+    public void testAuthenticateFailsIfUserDisabled() {
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("peter", "opal");
+
+        SiteminderAuthenticationProvider provider = new SiteminderAuthenticationProvider();
+        provider.setUserDetailsService(new MockUserDetailsServiceUserPeter());
+        provider.setUserCache(new MockUserCache());
+
+        try {
+            provider.authenticate(token);
+            fail("Should have thrown DisabledException");
+        } catch (DisabledException expected) {
+            assertTrue(true);
+        }
+    }
+
+    public void testAuthenticateFailsWhenUserDetailsServiceHasBackendFailure() {
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa", "koala");
+
+        SiteminderAuthenticationProvider provider = new SiteminderAuthenticationProvider();
+        provider.setUserDetailsService(new MockUserDetailsServiceSimulateBackendError());
+        provider.setUserCache(new MockUserCache());
+
+        try {
+            provider.authenticate(token);
+            fail("Should have thrown AuthenticationServiceException");
+        } catch (AuthenticationServiceException expected) {
+            assertTrue(true);
+        }
+    }
+
+    public void testAuthenticateFailsWithEmptyUsername() {
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(null, "koala");
+
+        SiteminderAuthenticationProvider provider = new SiteminderAuthenticationProvider();
+        provider.setUserDetailsService(new MockUserDetailsServiceUserMarissa());
+        provider.setUserCache(new MockUserCache());
+
+        try {
+            provider.authenticate(token);
+            fail("Should have thrown BadCredentialsException");
+        } catch (BadCredentialsException expected) {
+            assertTrue(true);
+        }
+    }
+
+    public void testAuthenticateFailsWithInvalidUsernameAndHideUserNotFoundExceptionFalse() {
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("INVALID_USER", "koala");
+
+        SiteminderAuthenticationProvider provider = new SiteminderAuthenticationProvider();
+        provider.setHideUserNotFoundExceptions(false); // we want UsernameNotFoundExceptions
+        provider.setUserDetailsService(new MockUserDetailsServiceUserMarissa());
+        provider.setUserCache(new MockUserCache());
+
+        try {
+            provider.authenticate(token);
+            fail("Should have thrown UsernameNotFoundException");
+        } catch (UsernameNotFoundException expected) {
+            assertTrue(true);
+        }
+    }
+
+    public void testAuthenticateFailsWithInvalidUsernameAndHideUserNotFoundExceptionsWithDefaultOfTrue() {
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("INVALID_USER", "koala");
+
+        SiteminderAuthenticationProvider provider = new SiteminderAuthenticationProvider();
+        assertTrue(provider.isHideUserNotFoundExceptions());
+        provider.setUserDetailsService(new MockUserDetailsServiceUserMarissa());
+        provider.setUserCache(new MockUserCache());
+
+        try {
+            provider.authenticate(token);
+            fail("Should have thrown BadCredentialsException");
+        } catch (BadCredentialsException expected) {
+            assertTrue(true);
+        }
+    }
+
+    public void testAuthenticateFailsWithMixedCaseUsernameIfDefaultChanged() {
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("MaRiSSA", "koala");
+
+        SiteminderAuthenticationProvider provider = new SiteminderAuthenticationProvider();
+        provider.setUserDetailsService(new MockUserDetailsServiceUserMarissa());
+        provider.setUserCache(new MockUserCache());
+
+        try {
+            provider.authenticate(token);
+            fail("Should have thrown BadCredentialsException");
+        } catch (BadCredentialsException expected) {
+            assertTrue(true);
+        }
+    }
+
+    public void testAuthenticates() {
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa", "koala");
+        token.setDetails("192.168.0.1");
+
+        SiteminderAuthenticationProvider provider = new SiteminderAuthenticationProvider();
+        provider.setUserDetailsService(new MockUserDetailsServiceUserMarissa());
+        provider.setUserCache(new MockUserCache());
+
+        Authentication result = provider.authenticate(token);
+
+        if (!(result instanceof UsernamePasswordAuthenticationToken)) {
+            fail("Should have returned instance of UsernamePasswordAuthenticationToken");
+        }
+
+        UsernamePasswordAuthenticationToken castResult = (UsernamePasswordAuthenticationToken) result;
+        assertEquals(User.class, castResult.getPrincipal().getClass());
+        assertEquals("koala", castResult.getCredentials());
+        assertEquals("ROLE_ONE", castResult.getAuthorities()[0].getAuthority());
+        assertEquals("ROLE_TWO", castResult.getAuthorities()[1].getAuthority());
+        assertEquals("192.168.0.1", castResult.getDetails());
+    }
+
+    public void testAuthenticatesASecondTime() {
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa", "koala");
+
+        SiteminderAuthenticationProvider provider = new SiteminderAuthenticationProvider();
+        provider.setUserDetailsService(new MockUserDetailsServiceUserMarissa());
+        provider.setUserCache(new MockUserCache());
+
+        Authentication result = provider.authenticate(token);
+
+        if (!(result instanceof UsernamePasswordAuthenticationToken)) {
+            fail("Should have returned instance of UsernamePasswordAuthenticationToken");
+        }
+
+        // Now try to authenticate with the previous result (with its UserDetails)
+        Authentication result2 = provider.authenticate(result);
+
+        if (!(result2 instanceof UsernamePasswordAuthenticationToken)) {
+            fail("Should have returned instance of UsernamePasswordAuthenticationToken");
+        }
+
+        assertEquals(result.getCredentials(), result2.getCredentials());
+    }
+
+    public void testAuthenticatesWithForcePrincipalAsString() {
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa", "koala");
+
+        SiteminderAuthenticationProvider provider = new SiteminderAuthenticationProvider();
+        provider.setUserDetailsService(new MockUserDetailsServiceUserMarissa());
+        provider.setUserCache(new MockUserCache());
+        provider.setForcePrincipalAsString(true);
+
+        Authentication result = provider.authenticate(token);
+
+        if (!(result instanceof UsernamePasswordAuthenticationToken)) {
+            fail("Should have returned instance of UsernamePasswordAuthenticationToken");
+        }
+
+        UsernamePasswordAuthenticationToken castResult = (UsernamePasswordAuthenticationToken) result;
+        assertEquals(String.class, castResult.getPrincipal().getClass());
+        assertEquals("marissa", castResult.getPrincipal());
+    }
+
+    public void testDetectsNullBeingReturnedFromUserDetailsService() {
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa", "koala");
+
+        SiteminderAuthenticationProvider provider = new SiteminderAuthenticationProvider();
+        provider.setUserDetailsService(new MockUserDetailsServiceReturnsNull());
+
+        try {
+            provider.authenticate(token);
+            fail("Should have thrown AuthenticationServiceException");
+        } catch (AuthenticationServiceException expected) {
+            assertEquals("UserDetailsService returned null, which is an interface contract violation", expected
+                    .getMessage());
+        }
+    }
+
+    public void testGettersSetters() {
+        SiteminderAuthenticationProvider provider = new SiteminderAuthenticationProvider();
+
+        provider.setUserCache(new EhCacheBasedUserCache());
+        assertEquals(EhCacheBasedUserCache.class, provider.getUserCache().getClass());
+
+        assertFalse(provider.isForcePrincipalAsString());
+        provider.setForcePrincipalAsString(true);
+        assertTrue(provider.isForcePrincipalAsString());
+    }
+
+    public void testStartupFailsIfNoUserDetailsService() throws Exception {
+        SiteminderAuthenticationProvider provider = new SiteminderAuthenticationProvider();
+
+        try {
+            provider.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertTrue(true);
+        }
+    }
+
+    public void testStartupFailsIfNoUserCacheSet() throws Exception {
+        SiteminderAuthenticationProvider provider = new SiteminderAuthenticationProvider();
+        provider.setUserDetailsService(new MockUserDetailsServiceUserMarissa());
+        assertEquals(NullUserCache.class, provider.getUserCache().getClass());
+        provider.setUserCache(null);
+
+        try {
+            provider.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertTrue(true);
+        }
+    }
+
+    public void testStartupSuccess() throws Exception {
+        SiteminderAuthenticationProvider provider = new SiteminderAuthenticationProvider();
+        UserDetailsService userDetailsService = new MockUserDetailsServiceUserMarissa();
+        provider.setUserDetailsService(userDetailsService);
+        provider.setUserCache(new MockUserCache());
+        assertEquals(userDetailsService, provider.getUserDetailsService());
+        provider.afterPropertiesSet();
+        assertTrue(true);
+    }
+
+    public void testSupports() {
+        SiteminderAuthenticationProvider provider = new SiteminderAuthenticationProvider();
+        assertTrue(provider.supports(UsernamePasswordAuthenticationToken.class));
+        assertTrue(!provider.supports(TestingAuthenticationToken.class));
+    }
+
+    //~ Inner Classes ==================================================================================================
+
+    private class MockUserDetailsServiceReturnsNull implements UserDetailsService {
+        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
+            return null;
+        }
+    }
+
+    private class MockUserDetailsServiceSimulateBackendError implements UserDetailsService {
+        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
+            throw new DataRetrievalFailureException("This mock simulator is designed to fail");
+        }
+    }
+
+    private class MockUserDetailsServiceUserMarissa implements UserDetailsService {
+        private String password = "koala";
+
+        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
+            if ("marissa".equals(username)) {
+                return new User("marissa", password, true, true, true, true, new GrantedAuthority[] {
+                        new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_TWO") });
+            } else {
+                throw new UsernameNotFoundException("Could not find: " + username);
+            }
+        }
+
+        public void setPassword(String password) {
+            this.password = password;
+        }
+    }
+
+    private class MockUserDetailsServiceUserMarissaWithSalt implements UserDetailsService {
+        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
+            if ("marissa".equals(username)) {
+                return new User("marissa", "koala{SYSTEM_SALT_VALUE}", true, true, true, true, new GrantedAuthority[] {
+                        new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_TWO") });
+            } else {
+                throw new UsernameNotFoundException("Could not find: " + username);
+            }
+        }
+    }
+
+    private class MockUserDetailsServiceUserPeter implements UserDetailsService {
+        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
+            if ("peter".equals(username)) {
+                return new User("peter", "opal", false, true, true, true, new GrantedAuthority[] {
+                        new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_TWO") });
+            } else {
+                throw new UsernameNotFoundException("Could not find: " + username);
+            }
+        }
+    }
+
+    private class MockUserDetailsServiceUserPeterAccountExpired implements UserDetailsService {
+        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
+            if ("peter".equals(username)) {
+                return new User("peter", "opal", true, false, true, true, new GrantedAuthority[] {
+                        new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_TWO") });
+            } else {
+                throw new UsernameNotFoundException("Could not find: " + username);
+            }
+        }
+    }
+
+    private class MockUserDetailsServiceUserPeterAccountLocked implements UserDetailsService {
+        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
+            if ("peter".equals(username)) {
+                return new User("peter", "opal", true, true, true, false, new GrantedAuthority[] {
+                        new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_TWO") });
+            } else {
+                throw new UsernameNotFoundException("Could not find: " + username);
+            }
+        }
+    }
+
+    private class MockUserDetailsServiceUserPeterCredentialsExpired implements UserDetailsService {
+        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
+            if ("peter".equals(username)) {
+                return new User("peter", "opal", true, true, false, true, new GrantedAuthority[] {
+                        new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_TWO") });
+            } else {
+                throw new UsernameNotFoundException("Could not find: " + username);
+            }
+        }
+    }
+
+    private class MockUserCache implements UserCache {
+        private Map cache = new HashMap();
+
+        public UserDetails getUserFromCache(String username) {
+            return (User) cache.get(username);
+        }
+
+        public void putUserInCache(UserDetails user) {
+            cache.put(user.getUsername(), user);
+        }
+
+        public void removeUserFromCache(String username) {
+        }
+    }
+}

+ 10 - 23
core/src/test/java/org/acegisecurity/ui/webapp/SiteminderAuthenticationProcessingFilterTests.java

@@ -19,13 +19,10 @@ import junit.framework.TestCase;
 
 
 import org.acegisecurity.Authentication;
 import org.acegisecurity.Authentication;
 import org.acegisecurity.MockAuthenticationManager;
 import org.acegisecurity.MockAuthenticationManager;
-
 import org.acegisecurity.ui.WebAuthenticationDetails;
 import org.acegisecurity.ui.WebAuthenticationDetails;
-
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.mock.web.MockHttpServletResponse;
 
 
-
 /**
 /**
  * Tests SiteminderAuthenticationProcessingFilter.
  * Tests SiteminderAuthenticationProcessingFilter.
  *
  *
@@ -36,18 +33,18 @@ import org.springframework.mock.web.MockHttpServletResponse;
 public class SiteminderAuthenticationProcessingFilterTests extends TestCase {
 public class SiteminderAuthenticationProcessingFilterTests extends TestCase {
     //~ Constructors ===================================================================================================
     //~ Constructors ===================================================================================================
 
 
-/**
-         * Basic constructor.
-         */
+    /**
+     * Basic constructor.
+     */
     public SiteminderAuthenticationProcessingFilterTests() {
     public SiteminderAuthenticationProcessingFilterTests() {
         super();
         super();
     }
     }
 
 
-/**
-         * Argument constructor.
-         * 
-         * @param arg0
-         */
+    /**
+     * Argument constructor.
+     * 
+     * @param arg0
+     */
     public SiteminderAuthenticationProcessingFilterTests(String arg0) {
     public SiteminderAuthenticationProcessingFilterTests(String arg0) {
         super(arg0);
         super(arg0);
     }
     }
@@ -92,15 +89,9 @@ public class SiteminderAuthenticationProcessingFilterTests extends TestCase {
         filter.setFilterProcessesUrl("foobar");
         filter.setFilterProcessesUrl("foobar");
         assertEquals("foobar", filter.getFilterProcessesUrl());
         assertEquals("foobar", filter.getFilterProcessesUrl());
 
 
-        filter.setFormPasswordParameterKey("passwordParamKey");
-        assertEquals("passwordParamKey", filter.getFormPasswordParameterKey());
-
         filter.setFormUsernameParameterKey("usernameParamKey");
         filter.setFormUsernameParameterKey("usernameParamKey");
         assertEquals("usernameParamKey", filter.getFormUsernameParameterKey());
         assertEquals("usernameParamKey", filter.getFormUsernameParameterKey());
 
 
-        filter.setSiteminderPasswordHeaderKey("passwordHeaderKey");
-        assertEquals("passwordHeaderKey", filter.getSiteminderPasswordHeaderKey());
-
         filter.setSiteminderUsernameHeaderKey("usernameHeaderKey");
         filter.setSiteminderUsernameHeaderKey("usernameHeaderKey");
         assertEquals("usernameHeaderKey", filter.getSiteminderUsernameHeaderKey());
         assertEquals("usernameHeaderKey", filter.getSiteminderUsernameHeaderKey());
     }
     }
@@ -131,8 +122,7 @@ public class SiteminderAuthenticationProcessingFilterTests extends TestCase {
      *
      *
      * @throws Exception
      * @throws Exception
      */
      */
-    public void testFormNullPasswordHandledGracefully()
-        throws Exception {
+    public void testFormNullPasswordHandledGracefully() throws Exception {
         MockHttpServletRequest request = new MockHttpServletRequest();
         MockHttpServletRequest request = new MockHttpServletRequest();
         request.addParameter(SiteminderAuthenticationProcessingFilter.ACEGI_SECURITY_FORM_USERNAME_KEY, "marissa");
         request.addParameter(SiteminderAuthenticationProcessingFilter.ACEGI_SECURITY_FORM_USERNAME_KEY, "marissa");
 
 
@@ -151,8 +141,7 @@ public class SiteminderAuthenticationProcessingFilterTests extends TestCase {
      *
      *
      * @throws Exception
      * @throws Exception
      */
      */
-    public void testFormNullUsernameHandledGracefully()
-        throws Exception {
+    public void testFormNullUsernameHandledGracefully() throws Exception {
         MockHttpServletRequest request = new MockHttpServletRequest();
         MockHttpServletRequest request = new MockHttpServletRequest();
         request.addParameter(SiteminderAuthenticationProcessingFilter.ACEGI_SECURITY_FORM_PASSWORD_KEY, "koala");
         request.addParameter(SiteminderAuthenticationProcessingFilter.ACEGI_SECURITY_FORM_PASSWORD_KEY, "koala");
 
 
@@ -186,7 +175,6 @@ public class SiteminderAuthenticationProcessingFilterTests extends TestCase {
         filter.setAuthenticationManager(authMgrThatGrantsAccess);
         filter.setAuthenticationManager(authMgrThatGrantsAccess);
 
 
         filter.setSiteminderUsernameHeaderKey("SM_USER");
         filter.setSiteminderUsernameHeaderKey("SM_USER");
-        filter.setSiteminderPasswordHeaderKey("SM_USER");
         filter.init(null);
         filter.init(null);
 
 
         // Requests for an unknown URL should NOT require (re)authentication
         // Requests for an unknown URL should NOT require (re)authentication
@@ -220,7 +208,6 @@ public class SiteminderAuthenticationProcessingFilterTests extends TestCase {
         SiteminderAuthenticationProcessingFilter filter = new SiteminderAuthenticationProcessingFilter();
         SiteminderAuthenticationProcessingFilter filter = new SiteminderAuthenticationProcessingFilter();
         filter.setAuthenticationManager(authMgr);
         filter.setAuthenticationManager(authMgr);
         filter.setSiteminderUsernameHeaderKey("SM_USER");
         filter.setSiteminderUsernameHeaderKey("SM_USER");
-        filter.setSiteminderPasswordHeaderKey("SM_USER");
         filter.init(null);
         filter.init(null);
 
 
         Authentication result = filter.attemptAuthentication(request);
         Authentication result = filter.attemptAuthentication(request);

+ 32 - 44
doc/docbook/acegi.xml

@@ -2120,7 +2120,8 @@ if (obj instanceof UserDetails) {
         Associates.</para>
         Associates.</para>
 
 
         <para>Acegi Security provides a filter,
         <para>Acegi Security provides a filter,
-        <literal>SiteminderAuthenticationProcessingFilter</literal>) that can
+        <literal>SiteminderAuthenticationProcessingFilter</literal> and
+        provider, <literal>SiteminderAuthenticationProvider</literal> that can
         be used to process requests that have been pre-authenticated by
         be used to process requests that have been pre-authenticated by
         Siteminder. This filter assumes that you're using Siteminder for
         Siteminder. This filter assumes that you're using Siteminder for
         <emphasis>authentication</emphasis>, and that you're using Acegi
         <emphasis>authentication</emphasis>, and that you're using Acegi
@@ -2128,13 +2129,14 @@ if (obj instanceof UserDetails) {
         for <emphasis>authorization</emphasis> is not yet directly supported
         for <emphasis>authorization</emphasis> is not yet directly supported
         by Acegi Security.</para>
         by Acegi Security.</para>
 
 
-        <para>In Siteminder, an agent is setup on your web server to intercept
-        a principal's first call to your application. The agent redirect the
-        web request to a single sign on login page, and then your application
-        receives the request. Inside the HTTP headers is a header - such as
-        <literal>SM_USER</literal> - which identifies the authenticated
-        principal. Please refer to your organization's "single sign-on" group
-        for header details in your particular configuration.</para>
+        <para>When using Siteminder, an agent is setup on your web server to
+        intercept a principal's first call to your application. The agent
+        redirects the web request to a single sign-on login page, and once
+        authenticated, your application receives the request. Inside the HTTP
+        request is a header - such as <literal>SM_USER</literal> - which
+        identifies the authenticated principal (please refer to your
+        organization's "single sign-on" group for header details in your
+        particular configuration).</para>
       </sect1>
       </sect1>
 
 
       <sect1 id="siteminder-config">
       <sect1 id="siteminder-config">
@@ -2142,9 +2144,9 @@ if (obj instanceof UserDetails) {
 
 
         <para>The first step in setting up Acegi Security's Siteminder support
         <para>The first step in setting up Acegi Security's Siteminder support
         is to define the authentication mechanism that will inspect the HTTP
         is to define the authentication mechanism that will inspect the HTTP
-        header discussed earlier. It will then generate a
-        <literal>UsernamePasswordAuthenticationToken</literal> that can later
-        on be sent to the <literal>DaoAuthenticationProvider</literal>. Let's
+        header discussed earlier. It will be responsible for generating a
+        <literal>UsernamePasswordAuthenticationToken</literal> that is later
+        sent to the <literal>SiteminderAuthenticationProvider</literal>. Let's
         look at an example:</para>
         look at an example:</para>
 
 
         <para><programlisting>&lt;bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.SiteminderAuthenticationProcessingFilter"&gt;
         <para><programlisting>&lt;bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.SiteminderAuthenticationProcessingFilter"&gt;
@@ -2153,51 +2155,37 @@ if (obj instanceof UserDetails) {
   &lt;property name="defaultTargetUrl"&gt;&lt;value&gt;/security.do?method=getMainMenu&lt;/value&gt;&lt;/property&gt;
   &lt;property name="defaultTargetUrl"&gt;&lt;value&gt;/security.do?method=getMainMenu&lt;/value&gt;&lt;/property&gt;
   &lt;property name="filterProcessesUrl"&gt;&lt;value&gt;/j_acegi_security_check&lt;/value&gt;&lt;/property&gt;
   &lt;property name="filterProcessesUrl"&gt;&lt;value&gt;/j_acegi_security_check&lt;/value&gt;&lt;/property&gt;
   &lt;property name="siteminderUsernameHeaderKey"&gt;&lt;value&gt;SM_USER&lt;/value&gt;&lt;/property&gt;
   &lt;property name="siteminderUsernameHeaderKey"&gt;&lt;value&gt;SM_USER&lt;/value&gt;&lt;/property&gt;
-  &lt;property name="siteminderPasswordHeaderKey"&gt;&lt;value&gt;SM_USER&lt;/value&gt;&lt;/property&gt;
+  &lt;property name="formUsernameParameterKey"&gt;&lt;value&gt;j_username&lt;/value&gt;&lt;/property&gt;
 &lt;/bean&gt;</programlisting></para>
 &lt;/bean&gt;</programlisting></para>
 
 
         <para>In our example above, the bean is being provided an
         <para>In our example above, the bean is being provided an
         <literal>AuthenticationManager</literal>, as is normally needed by
         <literal>AuthenticationManager</literal>, as is normally needed by
         authentication mechanisms. Several URLs are also specified, with the
         authentication mechanisms. Several URLs are also specified, with the
         values being self-explanatory. It's important to also specify the HTTP
         values being self-explanatory. It's important to also specify the HTTP
-        headers that Acegi Security should inspect. Most people won't need the
-        password value since Siteminder has already authenticated the user, so
-        it's typical to use the same header for both.</para>
+        header that Acegi Security should inspect. If you additionally want to
+        support form-based authentication (i.e. in your development
+        environment where Siteminder is not installed), specify the form's
+        username parameter as well - just don't do this in production!</para>
 
 
         <para>Note that you'll need a
         <para>Note that you'll need a
-        <literal><literal>DaoAuthenticationProvider</literal></literal>
+        <literal><literal>SiteminderAuthenticationProvider</literal></literal>
         configured against your <literal>ProviderManager</literal> in order to
         configured against your <literal>ProviderManager</literal> in order to
-        use the Siteminder authentication mechanism. Normally a
-        <literal>DaoAuthenticationProvider</literal> expects the password
+        use the Siteminder authentication mechanism. Normally an
+        <literal>AuthenticationProvider</literal> expects the password
         property to match what it retrieves from the
         property to match what it retrieves from the
-        <literal>UserDetailsSource</literal>. In this case, authentication has
-        already been handled by Siteminder and you've specified the same HTTP
-        header for both username and password. As such, you must modify the
-        code of <literal>DaoAuthenticationProvider</literal> to simply make
-        sure the username and password values match. This may sound like a
-        security weakness, but remember that users have to authenticate with
-        Siteminder before your application ever receives the requests, so the
-        purpose of your custom <literal>DaoAuthenticationProvider</literal>
-        should simply be to build the complete
-        <literal>Authentication</literal> object (ie with suitable
+        <literal>UserDetailsSource</literal>, but in this case, authentication
+        has already been handled by Siteminder, so password property is not
+        even relevant. This may sound like a security weakness, but remember
+        that users have to authenticate with Siteminder before your
+        application ever receives the requests, so the purpose of your custom
+        <literal>UserDetailsService</literal> should simply be to build the
+        complete <literal>Authentication</literal> object (ie with suitable
         <literal>GrantedAuthority[]</literal>s).</para>
         <literal>GrantedAuthority[]</literal>s).</para>
 
 
-        <para>Advanced tip and word to the wise: the
-        <literal>SiteminderAuthenticationProcessingFilter</literal> actually
-        extends <literal>AuthenticationProcessingFilter</literal> and thus
-        additionally supports form validation. If you configure the filter to
-        support both, and code your
-        <literal>daoAuthenticationProvider</literal> to match the username and
-        passwords as described above, you'll potentially defeat any security
-        you have in place if the web server's Siteminder agent is deactivated.
-        Don't do this, especially in production!</para>
-
-        <para>TODO: We should write a dedicated
-        <literal>AuthenticationProvider</literal> rather than require users to
-        modify their <literal>DaoAuthenticationProvider</literal>. Also review
-        the mixed use of SiteminderAuthenticationProcessingFilter, as it's
-        inconsistent with the rest of Acegi Security's authentication
-        mechanisms which are high cohesion.</para>
+        <para>Advanced tip and word to the wise: If you additionally want to
+        support form-based authentication in your development environment
+        (where Siteminder is typically not installed), specify the form's
+        username parameter as well. Just don't do this in production!</para>
       </sect1>
       </sect1>
     </chapter>
     </chapter>