فهرست منبع

SEC-234: Allow pluggable AuthenticationDetailsSource strategy interface.

Ben Alex 19 سال پیش
والد
کامیت
a47a342ce6

+ 12 - 3
core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousProcessingFilter.java

@@ -19,7 +19,8 @@ import org.acegisecurity.Authentication;
 
 import org.acegisecurity.context.SecurityContextHolder;
 
-import org.acegisecurity.ui.WebAuthenticationDetails;
+import org.acegisecurity.ui.AuthenticationDetailsSource;
+import org.acegisecurity.ui.AuthenticationDetailsSourceImpl;
 
 import org.acegisecurity.userdetails.memory.UserAttribute;
 
@@ -61,6 +62,7 @@ public class AnonymousProcessingFilter implements Filter, InitializingBean {
 
     //~ Instance fields ========================================================
 
+    private AuthenticationDetailsSource authenticationDetailsSource = new AuthenticationDetailsSourceImpl();
     private String key;
     private UserAttribute userAttribute;
     private boolean removeAfterRequest = true;
@@ -96,8 +98,8 @@ public class AnonymousProcessingFilter implements Filter, InitializingBean {
 
         AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,
                 userAttribute.getPassword(), userAttribute.getAuthorities());
-        auth.setDetails(new WebAuthenticationDetails(
-                (HttpServletRequest) request, false));
+        auth.setDetails(authenticationDetailsSource.buildDetails(
+                (HttpServletRequest) request));
 
         return auth;
     }
@@ -167,6 +169,13 @@ public class AnonymousProcessingFilter implements Filter, InitializingBean {
         return removeAfterRequest;
     }
 
+    public void setAuthenticationDetailsSource(
+        AuthenticationDetailsSource authenticationDetailsSource) {
+        Assert.notNull(authenticationDetailsSource,
+            "AuthenticationDetailsSource required");
+        this.authenticationDetailsSource = authenticationDetailsSource;
+    }
+
     public void setKey(String key) {
         this.key = key;
     }

+ 8 - 0
core/src/main/java/org/acegisecurity/ui/AbstractProcessingFilter.java

@@ -143,6 +143,7 @@ public abstract class AbstractProcessingFilter implements Filter,
     //~ Instance fields ========================================================
 
     protected ApplicationEventPublisher eventPublisher;
+    protected AuthenticationDetailsSource authenticationDetailsSource = new AuthenticationDetailsSourceImpl();
     private AuthenticationManager authenticationManager;
     protected final Log logger = LogFactory.getLog(this.getClass());
     protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
@@ -371,6 +372,13 @@ public abstract class AbstractProcessingFilter implements Filter,
         this.eventPublisher = eventPublisher;
     }
 
+    public void setAuthenticationDetailsSource(
+        AuthenticationDetailsSource authenticationDetailsSource) {
+        Assert.notNull(authenticationDetailsSource,
+            "AuthenticationDetailsSource required");
+        this.authenticationDetailsSource = authenticationDetailsSource;
+    }
+
     public void setAuthenticationFailureUrl(String authenticationFailureUrl) {
         this.authenticationFailureUrl = authenticationFailureUrl;
     }

+ 41 - 0
core/src/main/java/org/acegisecurity/ui/AuthenticationDetailsSource.java

@@ -0,0 +1,41 @@
+/* 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.ui;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * Provides a {@link org.acegisecurity.Authentication#getDetails()} object for
+ * a given web request.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface AuthenticationDetailsSource {
+    //~ Methods ================================================================
+
+    /**
+     * Called by a class when it wishes a new authentication details instance
+     * to be created.
+     *
+     * @param request the request object, which may be used by the
+     *        authentication details object
+     *
+     * @return a fully-configured authentication details instance
+     */
+    public Object buildDetails(HttpServletRequest request);
+}

+ 69 - 0
core/src/main/java/org/acegisecurity/ui/AuthenticationDetailsSourceImpl.java

@@ -0,0 +1,69 @@
+/* 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.ui;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * Base implementation of {@link AuthenticationDetailsSource}.
+ * 
+ * <P>
+ * By default will create an instance of <code>WebAuthenticationDetails</code>.
+ * Any object that accepts a <code>HttpServletRequest</code> as its sole
+ * constructor can be used instead of this default.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AuthenticationDetailsSourceImpl
+    implements AuthenticationDetailsSource {
+    //~ Instance fields ========================================================
+
+    private Class clazz = WebAuthenticationDetails.class;
+
+    //~ Methods ================================================================
+
+    public Object buildDetails(HttpServletRequest request) {
+        try {
+            Constructor constructor = clazz.getConstructor(new Class[] {HttpServletRequest.class});
+
+            return constructor.newInstance(new Object[] {request});
+        } catch (NoSuchMethodException ex) {
+            ReflectionUtils.handleReflectionException(ex);
+        } catch (InvocationTargetException ex) {
+            ReflectionUtils.handleReflectionException(ex);
+        } catch (InstantiationException ex) {
+            ReflectionUtils.handleReflectionException(ex);
+        } catch (IllegalAccessException ex) {
+            ReflectionUtils.handleReflectionException(ex);
+        }
+
+        return null;
+    }
+
+    public void setClazz(Class clazz) {
+        Assert.notNull(clazz, "Class required");
+        this.clazz = clazz;
+    }
+}

+ 1 - 8
core/src/main/java/org/acegisecurity/ui/WebAuthenticationDetails.java

@@ -52,15 +52,8 @@ public class WebAuthenticationDetails implements SessionIdentifierAware,
      */
     public WebAuthenticationDetails(HttpServletRequest request) {
         this.remoteAddress = request.getRemoteAddr();
-        this.sessionId = request.getSession(true).getId();
-        doPopulateAdditionalInformation(request);
-    }
-
-    public WebAuthenticationDetails(HttpServletRequest request,
-        boolean forceSessionCreation) {
-        this.remoteAddress = request.getRemoteAddr();
 
-        HttpSession session = request.getSession(forceSessionCreation);
+        HttpSession session = request.getSession(false);
         this.sessionId = (session != null) ? session.getId() : null;
 
         doPopulateAdditionalInformation(request);

+ 9 - 3
core/src/main/java/org/acegisecurity/ui/basicauth/BasicProcessingFilter.java

@@ -23,8 +23,9 @@ import org.acegisecurity.context.SecurityContextHolder;
 
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 
+import org.acegisecurity.ui.AuthenticationDetailsSource;
+import org.acegisecurity.ui.AuthenticationDetailsSourceImpl;
 import org.acegisecurity.ui.AuthenticationEntryPoint;
-import org.acegisecurity.ui.WebAuthenticationDetails;
 
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.logging.Log;
@@ -115,9 +116,15 @@ public class BasicProcessingFilter implements Filter, InitializingBean {
     private AuthenticationEntryPoint authenticationEntryPoint;
     private AuthenticationManager authenticationManager;
     private boolean ignoreFailure = false;
+    private AuthenticationDetailsSource authenticationDetailsSource = new AuthenticationDetailsSourceImpl();
 
     //~ Methods ================================================================
 
+    public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) {
+    	Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
+		this.authenticationDetailsSource = authenticationDetailsSource;
+	}
+
     public void afterPropertiesSet() throws Exception {
         Assert.notNull(this.authenticationManager,
             "An AuthenticationManager is required");
@@ -168,8 +175,7 @@ public class BasicProcessingFilter implements Filter, InitializingBean {
                 || !existingAuth.isAuthenticated()) {
                 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username,
                         password);
-                authRequest.setDetails(new WebAuthenticationDetails(
-                        httpRequest, false));
+                authRequest.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request));
 
                 Authentication authResult;
 

+ 1 - 2
core/src/main/java/org/acegisecurity/ui/cas/CasProcessingFilter.java

@@ -19,7 +19,6 @@ import org.acegisecurity.Authentication;
 import org.acegisecurity.AuthenticationException;
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 import org.acegisecurity.ui.AbstractProcessingFilter;
-import org.acegisecurity.ui.WebAuthenticationDetails;
 
 import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
@@ -105,7 +104,7 @@ public class CasProcessingFilter extends AbstractProcessingFilter {
         UsernamePasswordAuthenticationToken authRequest =
                 new UsernamePasswordAuthenticationToken(username, password);
 
-        authRequest.setDetails(new WebAuthenticationDetails(request, false));
+        authRequest.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request));
 
         return this.getAuthenticationManager().authenticate(authRequest);
     }

+ 51 - 30
core/src/main/java/org/acegisecurity/ui/digestauth/DigestProcessingFilter.java

@@ -1,4 +1,4 @@
-/* Copyright 2004, 2005 Acegi Technology Pty Limited
+/* 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.
@@ -15,42 +15,53 @@
 
 package org.acegisecurity.ui.digestauth;
 
-import java.io.IOException;
-import java.util.Map;
-
-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 org.acegisecurity.AcegiMessageSource;
 import org.acegisecurity.AuthenticationException;
 import org.acegisecurity.AuthenticationServiceException;
 import org.acegisecurity.BadCredentialsException;
+
 import org.acegisecurity.context.SecurityContextHolder;
+
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 import org.acegisecurity.providers.dao.UserCache;
 import org.acegisecurity.providers.dao.cache.NullUserCache;
-import org.acegisecurity.ui.WebAuthenticationDetails;
+
+import org.acegisecurity.ui.AuthenticationDetailsSource;
+import org.acegisecurity.ui.AuthenticationDetailsSourceImpl;
+
 import org.acegisecurity.userdetails.UserDetails;
 import org.acegisecurity.userdetails.UserDetailsService;
 import org.acegisecurity.userdetails.UsernameNotFoundException;
+
 import org.acegisecurity.util.StringSplitUtils;
+
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+
 import org.springframework.beans.factory.InitializingBean;
+
 import org.springframework.context.MessageSource;
 import org.springframework.context.MessageSourceAware;
 import org.springframework.context.support.MessageSourceAccessor;
+
 import org.springframework.util.Assert;
 import org.springframework.util.StringUtils;
 
+import java.io.IOException;
+
+import java.util.Map;
+
+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;
+
 
 /**
  * Processes a HTTP request's Digest authorization headers, putting the result
@@ -84,9 +95,9 @@ import org.springframework.util.StringUtils;
  * 
  * <p>
  * If authentication fails, an {@link
- * org.acegisecurity.ui.AuthenticationEntryPoint
- * AuthenticationEntryPoint} implementation is called. This must always be
- * {@link DigestProcessingFilterEntryPoint}, which will prompt the user to
+ * org.acegisecurity.ui.AuthenticationEntryPoint AuthenticationEntryPoint}
+ * implementation is called. This must always be {@link
+ * DigestProcessingFilterEntryPoint}, which will prompt the user to
  * authenticate again via Digest authentication.
  * </p>
  * 
@@ -112,10 +123,11 @@ public class DigestProcessingFilter implements Filter, InitializingBean,
 
     //~ Instance fields ========================================================
 
-    private UserDetailsService userDetailsService;
+    private AuthenticationDetailsSource authenticationDetailsSource = new AuthenticationDetailsSourceImpl();
     private DigestProcessingFilterEntryPoint authenticationEntryPoint;
     protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
     private UserCache userCache = new NullUserCache();
+    private UserDetailsService userDetailsService;
     private boolean passwordAlreadyEncoded = false;
 
     //~ Methods ================================================================
@@ -369,10 +381,11 @@ public class DigestProcessingFilter implements Filter, InitializingBean,
                     + "' with response: '" + responseDigest + "'");
             }
 
-            UsernamePasswordAuthenticationToken authRequest =
-                    new UsernamePasswordAuthenticationToken(user, user.getPassword());
+            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(user,
+                    user.getPassword());
 
-            authRequest.setDetails(new WebAuthenticationDetails(httpRequest, false));
+            authRequest.setDetails(authenticationDetailsSource.buildDetails(
+                    (HttpServletRequest) request));
 
             SecurityContextHolder.getContext().setAuthentication(authRequest);
         }
@@ -405,8 +418,8 @@ public class DigestProcessingFilter implements Filter, InitializingBean,
      * <code>response</code> independently. Provided as a static method to
      * simplify the coding of user agents.
      *
-     * @param passwordAlreadyEncoded true if the password argument is already encoded in
-     *                               the correct format. False if it is plain text.
+     * @param passwordAlreadyEncoded true if the password argument is already
+     *        encoded in the correct format. False if it is plain text.
      * @param username the user's login name.
      * @param realm the name of the realm.
      * @param password the user's password in plaintext or ready-encoded.
@@ -419,7 +432,8 @@ public class DigestProcessingFilter implements Filter, InitializingBean,
      *
      * @return the MD5 of the digest authentication response, encoded in hex
      *
-     * @throws IllegalArgumentException if the supplied qop value is unsupported.
+     * @throws IllegalArgumentException if the supplied qop value is
+     *         unsupported.
      */
     public static String generateDigest(boolean passwordAlreadyEncoded,
         String username, String realm, String password, String httpMethod,
@@ -454,10 +468,6 @@ public class DigestProcessingFilter implements Filter, InitializingBean,
         return digestMd5;
     }
 
-    public UserDetailsService getUserDetailsService() {
-        return userDetailsService;
-    }
-
     public DigestProcessingFilterEntryPoint getAuthenticationEntryPoint() {
         return authenticationEntryPoint;
     }
@@ -466,10 +476,17 @@ public class DigestProcessingFilter implements Filter, InitializingBean,
         return userCache;
     }
 
+    public UserDetailsService getUserDetailsService() {
+        return userDetailsService;
+    }
+
     public void init(FilterConfig ignored) throws ServletException {}
 
-    public void setUserDetailsService(UserDetailsService authenticationDao) {
-        this.userDetailsService = authenticationDao;
+    public void setAuthenticationDetailsSource(
+        AuthenticationDetailsSource authenticationDetailsSource) {
+        Assert.notNull(authenticationDetailsSource,
+            "AuthenticationDetailsSource required");
+        this.authenticationDetailsSource = authenticationDetailsSource;
     }
 
     public void setAuthenticationEntryPoint(
@@ -488,4 +505,8 @@ public class DigestProcessingFilter implements Filter, InitializingBean,
     public void setUserCache(UserCache userCache) {
         this.userCache = userCache;
     }
+
+    public void setUserDetailsService(UserDetailsService authenticationDao) {
+        this.userDetailsService = authenticationDao;
+    }
 }

+ 15 - 5
core/src/main/java/org/acegisecurity/ui/rememberme/TokenBasedRememberMeServices.java

@@ -19,7 +19,8 @@ import org.acegisecurity.Authentication;
 
 import org.acegisecurity.providers.rememberme.RememberMeAuthenticationToken;
 
-import org.acegisecurity.ui.WebAuthenticationDetails;
+import org.acegisecurity.ui.AuthenticationDetailsSource;
+import org.acegisecurity.ui.AuthenticationDetailsSourceImpl;
 
 import org.acegisecurity.userdetails.UserDetails;
 import org.acegisecurity.userdetails.UserDetailsService;
@@ -116,6 +117,7 @@ public class TokenBasedRememberMeServices implements RememberMeServices,
 
     //~ Instance fields ========================================================
 
+    private AuthenticationDetailsSource authenticationDetailsSource = new AuthenticationDetailsSourceImpl();
     private String key;
     private String parameter = DEFAULT_PARAMETER;
     private UserDetailsService userDetailsService;
@@ -232,8 +234,8 @@ public class TokenBasedRememberMeServices implements RememberMeServices,
 
                         RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(this.key,
                                 userDetails, userDetails.getAuthorities());
-                        auth.setDetails(new WebAuthenticationDetails(request,
-                                false));
+                        auth.setDetails(authenticationDetailsSource.buildDetails(
+                                (HttpServletRequest) request));
 
                         return auth;
                     } else {
@@ -347,15 +349,23 @@ public class TokenBasedRememberMeServices implements RememberMeServices,
         return cookie;
     }
 
-    protected Cookie makeValidCookie(long expiryTime, String tokenValueBase64, HttpServletRequest request) {
+    protected Cookie makeValidCookie(long expiryTime, String tokenValueBase64,
+        HttpServletRequest request) {
         Cookie cookie = new Cookie(ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,
                 tokenValueBase64);
         cookie.setMaxAge(60 * 60 * 24 * 365 * 5); // 5 years
         cookie.setPath(request.getContextPath());
-        
+
         return cookie;
     }
 
+    public void setAuthenticationDetailsSource(
+        AuthenticationDetailsSource authenticationDetailsSource) {
+        Assert.notNull(authenticationDetailsSource,
+            "AuthenticationDetailsSource required");
+        this.authenticationDetailsSource = authenticationDetailsSource;
+    }
+
     public void setKey(String key) {
         this.key = key;
     }

+ 325 - 339
core/src/main/java/org/acegisecurity/ui/switchuser/SwitchUserProcessingFilter.java

@@ -1,4 +1,4 @@
-/* Copyright 2004, 2005 Acegi Technology Pty Limited
+/* 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.
@@ -15,20 +15,6 @@
 
 package org.acegisecurity.ui.switchuser;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-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 org.acegisecurity.AccountExpiredException;
 import org.acegisecurity.AcegiMessageSource;
 import org.acegisecurity.Authentication;
@@ -38,24 +24,49 @@ import org.acegisecurity.CredentialsExpiredException;
 import org.acegisecurity.DisabledException;
 import org.acegisecurity.GrantedAuthority;
 import org.acegisecurity.LockedException;
+
 import org.acegisecurity.context.SecurityContextHolder;
+
 import org.acegisecurity.event.authentication.AuthenticationSwitchUserEvent;
+
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
-import org.acegisecurity.ui.WebAuthenticationDetails;
+
+import org.acegisecurity.ui.AuthenticationDetailsSource;
+import org.acegisecurity.ui.AuthenticationDetailsSourceImpl;
+
 import org.acegisecurity.userdetails.UserDetails;
 import org.acegisecurity.userdetails.UserDetailsService;
 import org.acegisecurity.userdetails.UsernameNotFoundException;
+
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+
 import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.InitializingBean;
+
 import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.context.ApplicationEventPublisherAware;
 import org.springframework.context.MessageSource;
 import org.springframework.context.MessageSourceAware;
 import org.springframework.context.support.MessageSourceAccessor;
+
 import org.springframework.util.Assert;
 
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+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;
+
 
 /**
  * Switch User processing filter responsible for user context switching.
@@ -104,7 +115,6 @@ import org.springframework.util.Assert;
  * </pre>
  * </p>
  *
- *
  * @author Mark St.Godard
  * @version $Id$
  *
@@ -124,15 +134,16 @@ public class SwitchUserProcessingFilter implements Filter, InitializingBean,
     //~ Instance fields ========================================================
 
     private ApplicationEventPublisher eventPublisher;
-
-    // ~ Instance fields
-    // ========================================================
-    private UserDetailsService userDetailsService;
+    private AuthenticationDetailsSource authenticationDetailsSource = new AuthenticationDetailsSourceImpl();
     protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
     private String exitUserUrl = "/j_acegi_exit_user";
     private String switchUserUrl = "/j_acegi_switch_user";
     private String targetUrl;
 
+    // ~ Instance fields
+    // ========================================================
+    private UserDetailsService userDetailsService;
+
     //~ Methods ================================================================
 
     public void afterPropertiesSet() throws Exception {
@@ -160,369 +171,344 @@ public class SwitchUserProcessingFilter implements Filter, InitializingBean,
         Authentication current = SecurityContextHolder.getContext()
                                                       .getAuthentication();
 
-            if (null == current) {
-                throw new AuthenticationCredentialsNotFoundException(messages
-                        .getMessage("SwitchUserProcessingFilter.noCurrentUser",
-                            "No current user associated with this request"));
-            }
+        if (null == current) {
+            throw new AuthenticationCredentialsNotFoundException(messages
+                .getMessage("SwitchUserProcessingFilter.noCurrentUser",
+                    "No current user associated with this request"));
+        }
 
-            // check to see if the current user did actual switch to another user
-            // if so, get the original source user so we can switch back
-            Authentication original = getSourceAuthentication(current);
-
-            if (original == null) {
-                logger.error(
-                    "Could not find original user Authentication object!");
-                throw new AuthenticationCredentialsNotFoundException(messages
-                        .getMessage(
-                            "SwitchUserProcessingFilter.noOriginalAuthentication",
-                            "Could not find original Authentication object"));
-            }
+        // check to see if the current user did actual switch to another user
+        // if so, get the original source user so we can switch back
+        Authentication original = getSourceAuthentication(current);
 
-            // get the source user details
-            UserDetails originalUser = null;
-            Object obj = original.getPrincipal();
+        if (original == null) {
+            logger.error("Could not find original user Authentication object!");
+            throw new AuthenticationCredentialsNotFoundException(messages
+                .getMessage("SwitchUserProcessingFilter.noOriginalAuthentication",
+                    "Could not find original Authentication object"));
+        }
 
-            if ((obj != null) && obj instanceof UserDetails) {
-                originalUser = (UserDetails) obj;
-            }
+        // get the source user details
+        UserDetails originalUser = null;
+        Object obj = original.getPrincipal();
 
-            // publish event
-            if (this.eventPublisher != null) {
-                eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(
-                        current, originalUser));
-            }
+        if ((obj != null) && obj instanceof UserDetails) {
+            originalUser = (UserDetails) obj;
+        }
 
-            return original;
+        // publish event
+        if (this.eventPublisher != null) {
+            eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(
+                    current, originalUser));
         }
 
-        /**
-         * Attempt to switch to another user. If the user does not exist or
-         * is not active, return null.
-         *
-         * @param request The http request
-         *
-         * @return The new <code>Authentication</code> request if
-         *         successfully switched to another user,
-         *         <code>null</code> otherwise.
-         *
-         * @throws AuthenticationException
-         * @throws UsernameNotFoundException If the target user is not
-         *         found.
-         * @throws LockedException DOCUMENT ME!
-         * @throws DisabledException If the target user is disabled.
-         * @throws AccountExpiredException If the target user account is
-         *         expired.
-         * @throws CredentialsExpiredException If the target user
-         *         credentials are expired.
-         */
-        protected Authentication attemptSwitchUser(
-                HttpServletRequest request) throws AuthenticationException {
-            UsernamePasswordAuthenticationToken targetUserRequest = null;
-
-            String username = request.getParameter(ACEGI_SECURITY_SWITCH_USERNAME_KEY);
-
-            if (username == null) {
-                username = "";
-            }
+        return original;
+    }
 
-            if (logger.isDebugEnabled()) {
-                logger.debug("Attempt to switch to user [" + username + "]");
-            }
+    /**
+     * Attempt to switch to another user. If the user does not exist or is not
+     * active, return null.
+     *
+     * @param request The http request
+     *
+     * @return The new <code>Authentication</code> request if successfully
+     *         switched to another user, <code>null</code> otherwise.
+     *
+     * @throws AuthenticationException
+     * @throws UsernameNotFoundException If the target user is not found.
+     * @throws LockedException DOCUMENT ME!
+     * @throws DisabledException If the target user is disabled.
+     * @throws AccountExpiredException If the target user account is expired.
+     * @throws CredentialsExpiredException If the target user credentials are
+     *         expired.
+     */
+    protected Authentication attemptSwitchUser(HttpServletRequest request)
+        throws AuthenticationException {
+        UsernamePasswordAuthenticationToken targetUserRequest = null;
 
-            // load the user by name
-            UserDetails targetUser = this.userDetailsService
-                    .loadUserByUsername(username);
+        String username = request.getParameter(ACEGI_SECURITY_SWITCH_USERNAME_KEY);
 
-            // user not found
-            if (targetUser == null) {
-                throw new UsernameNotFoundException(messages.getMessage(
-                        "SwitchUserProcessingFilter.usernameNotFound",
-                        new Object[] {username},
-                        "Username {0} not found"));
-            }
+        if (username == null) {
+            username = "";
+        }
 
-            // account is expired
-            if (!targetUser.isAccountNonLocked()) {
-                throw new LockedException(messages.getMessage(
-                        "SwitchUserProcessingFilter.locked",
-                        "User account is locked"));
-            }
+        if (logger.isDebugEnabled()) {
+            logger.debug("Attempt to switch to user [" + username + "]");
+        }
 
-            // user is disabled
-            if (!targetUser.isEnabled()) {
-                throw new DisabledException(messages.getMessage(
-                        "SwitchUserProcessingFilter.disabled",
-                        "User is disabled"));
-            }
+        // load the user by name
+        UserDetails targetUser = this.userDetailsService.loadUserByUsername(username);
 
-            // account is expired
-            if (!targetUser.isAccountNonExpired()) {
-                throw new AccountExpiredException(messages.getMessage(
-                        "SwitchUserProcessingFilter.expired",
-                        "User account has expired"));
-            }
+        // user not found
+        if (targetUser == null) {
+            throw new UsernameNotFoundException(messages.getMessage(
+                    "SwitchUserProcessingFilter.usernameNotFound",
+                    new Object[] {username}, "Username {0} not found"));
+        }
 
-            // credentials expired
-            if (!targetUser.isCredentialsNonExpired()) {
-                throw new CredentialsExpiredException(messages
-                        .getMessage(
-                            "SwitchUserProcessingFilter.credentialsExpired",
-                            "User credentials have expired"));
-            }
+        // account is expired
+        if (!targetUser.isAccountNonLocked()) {
+            throw new LockedException(messages.getMessage(
+                    "SwitchUserProcessingFilter.locked",
+                    "User account is locked"));
+        }
 
-            // ok, create the switch user token
-            targetUserRequest = createSwitchUserToken(request,
-                    username, targetUser);
+        // user is disabled
+        if (!targetUser.isEnabled()) {
+            throw new DisabledException(messages.getMessage(
+                    "SwitchUserProcessingFilter.disabled", "User is disabled"));
+        }
 
-            if (logger.isDebugEnabled()) {
-                logger.debug("Switch User Token [" + targetUserRequest + "]");
-            }
+        // account is expired
+        if (!targetUser.isAccountNonExpired()) {
+            throw new AccountExpiredException(messages.getMessage(
+                    "SwitchUserProcessingFilter.expired",
+                    "User account has expired"));
+        }
 
-            // publish event
-            if (this.eventPublisher != null) {
-                eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(
-                        SecurityContextHolder.getContext().getAuthentication(),
-                        targetUser));
-            }
+        // credentials expired
+        if (!targetUser.isCredentialsNonExpired()) {
+            throw new CredentialsExpiredException(messages.getMessage(
+                    "SwitchUserProcessingFilter.credentialsExpired",
+                    "User credentials have expired"));
+        }
+
+        // ok, create the switch user token
+        targetUserRequest = createSwitchUserToken(request, username, targetUser);
 
-            return targetUserRequest;
+        if (logger.isDebugEnabled()) {
+            logger.debug("Switch User Token [" + targetUserRequest + "]");
         }
 
-        /**
-         * Create a switch user token that contains an additional
-         * <tt>GrantedAuthority</tt> that contains the original
-         * <code>Authentication</code> object.
-         *
-         * @param request The http servlet request.
-         * @param username The username of target user
-         * @param targetUser The target user
-         *
-         * @return The authentication token
-         *
-         * @see SwitchUserGrantedAuthority
-         */
-        private UsernamePasswordAuthenticationToken createSwitchUserToken(
-            HttpServletRequest request, String username,
-            UserDetails targetUser) {
-            UsernamePasswordAuthenticationToken targetUserRequest;
-
-            // grant an additional authority that contains the original Authentication object
-            // which will be used to 'exit' from the current switched user.
-            Authentication currentAuth = SecurityContextHolder.getContext()
-                                                              .getAuthentication();
-            GrantedAuthority switchAuthority = new SwitchUserGrantedAuthority(ROLE_PREVIOUS_ADMINISTRATOR,
-                    currentAuth);
-
-            // get the original authorities
-            List orig = Arrays.asList(targetUser.getAuthorities());
-
-            // add the new switch user authority
-            List newAuths = new ArrayList(orig);
-            newAuths.add(switchAuthority);
-
-            GrantedAuthority[] authorities = {};
-            authorities = (GrantedAuthority[]) newAuths.toArray(authorities);
-
-            // create the new authentication token
-            targetUserRequest = new UsernamePasswordAuthenticationToken(targetUser,
-                    targetUser.getPassword(), authorities);
-
-            // set details
-            targetUserRequest.setDetails(new WebAuthenticationDetails(
-                    request, false));
-
-            return targetUserRequest;
+        // publish event
+        if (this.eventPublisher != null) {
+            eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(
+                    SecurityContextHolder.getContext().getAuthentication(),
+                    targetUser));
         }
 
-        public void destroy() {}
+        return targetUserRequest;
+    }
 
-        /**
-         * @see javax.servlet.Filter#doFilter
-         */
-        public void doFilter(ServletRequest request,
-            ServletResponse response, FilterChain chain)
-            throws IOException, ServletException {
-            Assert.isInstanceOf(HttpServletRequest.class, request);
-            Assert.isInstanceOf(HttpServletResponse.class, response);
+    /**
+     * Create a switch user token that contains an additional
+     * <tt>GrantedAuthority</tt> that contains the original
+     * <code>Authentication</code> object.
+     *
+     * @param request The http servlet request.
+     * @param username The username of target user
+     * @param targetUser The target user
+     *
+     * @return The authentication token
+     *
+     * @see SwitchUserGrantedAuthority
+     */
+    private UsernamePasswordAuthenticationToken createSwitchUserToken(
+        HttpServletRequest request, String username, UserDetails targetUser) {
+        UsernamePasswordAuthenticationToken targetUserRequest;
 
-            HttpServletRequest httpRequest = (HttpServletRequest) request;
-            HttpServletResponse httpResponse = (HttpServletResponse) response;
+        // grant an additional authority that contains the original Authentication object
+        // which will be used to 'exit' from the current switched user.
+        Authentication currentAuth = SecurityContextHolder.getContext()
+                                                          .getAuthentication();
+        GrantedAuthority switchAuthority = new SwitchUserGrantedAuthority(ROLE_PREVIOUS_ADMINISTRATOR,
+                currentAuth);
 
-            // check for switch or exit request
-            if (requiresSwitchUser(httpRequest)) {
-                // if set, attempt switch and store original
-                Authentication targetUser = attemptSwitchUser(httpRequest);
+        // get the original authorities
+        List orig = Arrays.asList(targetUser.getAuthorities());
 
-                // update the current context to the new target user
-                SecurityContextHolder.getContext()
-                                     .setAuthentication(targetUser);
+        // add the new switch user authority
+        List newAuths = new ArrayList(orig);
+        newAuths.add(switchAuthority);
 
-                // redirect to target url
-                httpResponse.sendRedirect(httpResponse
-                        .encodeRedirectURL(httpRequest
-                                .getContextPath() + targetUrl));
+        GrantedAuthority[] authorities = {};
+        authorities = (GrantedAuthority[]) newAuths.toArray(authorities);
 
-                return;
-            } else if (requiresExitUser(httpRequest)) {
-                // get the original authentication object (if exists)
-                Authentication originalUser = attemptExitUser(httpRequest);
+        // create the new authentication token
+        targetUserRequest = new UsernamePasswordAuthenticationToken(targetUser,
+                targetUser.getPassword(), authorities);
 
-                // update the current context back to the original user
-                SecurityContextHolder.getContext()
-                                     .setAuthentication(originalUser);
+        // set details
+        targetUserRequest.setDetails(authenticationDetailsSource.buildDetails(
+                (HttpServletRequest) request));
 
-                // redirect to target url
-                httpResponse.sendRedirect(httpResponse.encodeRedirectURL(
-                        httpRequest.getContextPath() + targetUrl));
+        return targetUserRequest;
+    }
 
-                return;
-            }
+    public void destroy() {}
 
-            chain.doFilter(request, response);
-        }
+    /**
+     * @see javax.servlet.Filter#doFilter
+     */
+    public void doFilter(ServletRequest request, ServletResponse response,
+        FilterChain chain) throws IOException, ServletException {
+        Assert.isInstanceOf(HttpServletRequest.class, request);
+        Assert.isInstanceOf(HttpServletResponse.class, response);
 
-        /**
-         * Find the original <code>Authentication</code> object from
-         * the current user's granted authorities. A successfully switched
-         * user should have a <code>SwitchUserGrantedAuthority</code>
-         * that contains the original source user <code>Authentication</code>
-         * object.
-         *
-         * @param current The current  <code>Authentication</code>
-         *        object
-         *
-         * @return The source user <code>Authentication</code>
-         *         object or <code>null</code> otherwise.
-         */
-        private Authentication getSourceAuthentication(
-            Authentication current) {
-            Authentication original = null;
-
-            // iterate over granted authorities and find the 'switch user' authority
-            GrantedAuthority[] authorities = current
-                .getAuthorities();
-
-            for (int i = 0; i < authorities.length; i++) {
-                // check for switch user type of authority
-                if (authorities[i] instanceof SwitchUserGrantedAuthority) {
-                    original = ((SwitchUserGrantedAuthority) authorities[i])
-                        .getSource();
-                    logger.debug(
-                        "Found original switch user granted authority ["
-                        + original + "]");
-                }
-            }
+        HttpServletRequest httpRequest = (HttpServletRequest) request;
+        HttpServletResponse httpResponse = (HttpServletResponse) response;
 
-            return original;
-        }
+        // check for switch or exit request
+        if (requiresSwitchUser(httpRequest)) {
+            // if set, attempt switch and store original
+            Authentication targetUser = attemptSwitchUser(httpRequest);
 
-        public void init(FilterConfig ignored)
-            throws ServletException {}
-
-        /**
-         * Checks the request URI for the presence
-         * of <tt>exitUserUrl</tt>.
-         *
-         * @param request The http servlet request
-         *
-         * @return <code>true</code> if the request requires a exit user,
-         *         <code>false</code> otherwise.
-         *
-         * @see SwitchUserProcessingFilter#exitUserUrl
-         */
-        protected boolean requiresExitUser(
-            HttpServletRequest request) {
-            String uri = stripUri(request);
-
-            return uri.endsWith(request
-                    .getContextPath() + exitUserUrl);
-        }
+            // update the current context to the new target user
+            SecurityContextHolder.getContext().setAuthentication(targetUser);
 
-        /**
-         * Checks the request URI for the presence of <tt>switchUserUrl</tt>.
-         *
-         * @param request The http servlet request
-         *
-         * @return <code>true</code> if the request requires a switch,
-         *         <code>false</code> otherwise.
-         *
-         * @see SwitchUserProcessingFilter#switchUserUrl
-         */
-        protected boolean requiresSwitchUser(
-            HttpServletRequest request) {
-            String uri = stripUri(request);
-
-            return uri.endsWith(request.getContextPath() + switchUserUrl);
-        }
+            // redirect to target url
+            httpResponse.sendRedirect(httpResponse.encodeRedirectURL(httpRequest
+                    .getContextPath() + targetUrl));
 
-        public void setApplicationEventPublisher(
-            ApplicationEventPublisher eventPublisher)
-            throws BeansException {
-            this.eventPublisher = eventPublisher;
-        }
+            return;
+        } else if (requiresExitUser(httpRequest)) {
+            // get the original authentication object (if exists)
+            Authentication originalUser = attemptExitUser(httpRequest);
 
-        /**
-         * Sets the authentication data access object.
-         *
-         * @param authenticationDao The authentication dao
-         */
-        public void setUserDetailsService(
-            UserDetailsService authenticationDao) {
-            this.userDetailsService = authenticationDao;
-        }
+            // update the current context back to the original user
+            SecurityContextHolder.getContext().setAuthentication(originalUser);
 
-        /**
-         * Set the URL to respond to exit user processing.
-         *
-         * @param exitUserUrl The exit user URL.
-         */
-        public void setExitUserUrl(
-            String exitUserUrl) {
-            this.exitUserUrl = exitUserUrl;
-        }
+            // redirect to target url
+            httpResponse.sendRedirect(httpResponse.encodeRedirectURL(httpRequest
+                    .getContextPath() + targetUrl));
 
-        public void setMessageSource(
-            MessageSource messageSource) {
-            this.messages = new MessageSourceAccessor(messageSource);
+            return;
         }
 
-        /**
-         * Set the URL to respond to switch user processing.
-         *
-         * @param switchUserUrl The switch user URL.
-         */
-        public void setSwitchUserUrl(String switchUserUrl) {
-            this.switchUserUrl = switchUserUrl;
-        }
+        chain.doFilter(request, response);
+    }
 
-        /**
-         * Sets the URL to go to after a successful switch / exit user
-         * request.
-         *
-         * @param targetUrl The target url.
-         */
-        public void setTargetUrl(
-            String targetUrl) {
-            this.targetUrl = targetUrl;
+    /**
+     * Find the original <code>Authentication</code> object from the current
+     * user's granted authorities. A successfully switched user should have a
+     * <code>SwitchUserGrantedAuthority</code> that contains the original
+     * source user <code>Authentication</code> object.
+     *
+     * @param current The current  <code>Authentication</code> object
+     *
+     * @return The source user <code>Authentication</code> object or
+     *         <code>null</code> otherwise.
+     */
+    private Authentication getSourceAuthentication(Authentication current) {
+        Authentication original = null;
+
+        // iterate over granted authorities and find the 'switch user' authority
+        GrantedAuthority[] authorities = current.getAuthorities();
+
+        for (int i = 0; i < authorities.length; i++) {
+            // check for switch user type of authority
+            if (authorities[i] instanceof SwitchUserGrantedAuthority) {
+                original = ((SwitchUserGrantedAuthority) authorities[i])
+                    .getSource();
+                logger.debug("Found original switch user granted authority ["
+                    + original + "]");
+            }
         }
 
-        /**
-         * Strips any content after the ';' in the request URI
-         *
-         * @param request The http request
-         *
-         * @return The stripped uri
-         */
-        private static String stripUri(HttpServletRequest request) {
-            String uri = request.getRequestURI();
-            int idx = uri.indexOf(';');
-
-            if (idx > 0) {
-                uri = uri.substring(0,
-                        idx);
-            }
+        return original;
+    }
+
+    public void init(FilterConfig ignored) throws ServletException {}
+
+    /**
+     * Checks the request URI for the presence of <tt>exitUserUrl</tt>.
+     *
+     * @param request The http servlet request
+     *
+     * @return <code>true</code> if the request requires a exit user,
+     *         <code>false</code> otherwise.
+     *
+     * @see SwitchUserProcessingFilter#exitUserUrl
+     */
+    protected boolean requiresExitUser(HttpServletRequest request) {
+        String uri = stripUri(request);
+
+        return uri.endsWith(request.getContextPath() + exitUserUrl);
+    }
+
+    /**
+     * Checks the request URI for the presence of <tt>switchUserUrl</tt>.
+     *
+     * @param request The http servlet request
+     *
+     * @return <code>true</code> if the request requires a switch,
+     *         <code>false</code> otherwise.
+     *
+     * @see SwitchUserProcessingFilter#switchUserUrl
+     */
+    protected boolean requiresSwitchUser(HttpServletRequest request) {
+        String uri = stripUri(request);
+
+        return uri.endsWith(request.getContextPath() + switchUserUrl);
+    }
+
+    public void setApplicationEventPublisher(
+        ApplicationEventPublisher eventPublisher) throws BeansException {
+        this.eventPublisher = eventPublisher;
+    }
+
+    public void setAuthenticationDetailsSource(
+        AuthenticationDetailsSource authenticationDetailsSource) {
+        Assert.notNull(authenticationDetailsSource,
+            "AuthenticationDetailsSource required");
+        this.authenticationDetailsSource = authenticationDetailsSource;
+    }
 
-            return uri;
+    /**
+     * Set the URL to respond to exit user processing.
+     *
+     * @param exitUserUrl The exit user URL.
+     */
+    public void setExitUserUrl(String exitUserUrl) {
+        this.exitUserUrl = exitUserUrl;
+    }
+
+    public void setMessageSource(MessageSource messageSource) {
+        this.messages = new MessageSourceAccessor(messageSource);
+    }
+
+    /**
+     * Set the URL to respond to switch user processing.
+     *
+     * @param switchUserUrl The switch user URL.
+     */
+    public void setSwitchUserUrl(String switchUserUrl) {
+        this.switchUserUrl = switchUserUrl;
+    }
+
+    /**
+     * Sets the URL to go to after a successful switch / exit user request.
+     *
+     * @param targetUrl The target url.
+     */
+    public void setTargetUrl(String targetUrl) {
+        this.targetUrl = targetUrl;
+    }
+
+    /**
+     * Sets the authentication data access object.
+     *
+     * @param authenticationDao The authentication dao
+     */
+    public void setUserDetailsService(UserDetailsService authenticationDao) {
+        this.userDetailsService = authenticationDao;
+    }
+
+    /**
+     * Strips any content after the ';' in the request URI
+     *
+     * @param request The http request
+     *
+     * @return The stripped uri
+     */
+    private static String stripUri(HttpServletRequest request) {
+        String uri = request.getRequestURI();
+        int idx = uri.indexOf(';');
+
+        if (idx > 0) {
+            uri = uri.substring(0, idx);
         }
+
+        return uri;
+    }
 }

+ 24 - 24
core/src/main/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilter.java

@@ -1,4 +1,4 @@
-/* Copyright 2004, 2005 Acegi Technology Pty Limited
+/* 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.
@@ -17,9 +17,10 @@ package org.acegisecurity.ui.webapp;
 
 import org.acegisecurity.Authentication;
 import org.acegisecurity.AuthenticationException;
+
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+
 import org.acegisecurity.ui.AbstractProcessingFilter;
-import org.acegisecurity.ui.WebAuthenticationDetails;
 
 import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
@@ -55,15 +56,6 @@ public class AuthenticationProcessingFilter extends AbstractProcessingFilter {
 
     //~ Methods ================================================================
 
-    /**
-     * This filter by default responds to <code>/j_acegi_security_check</code>.
-     *
-     * @return the default
-     */
-    public String getDefaultFilterProcessesUrl() {
-        return "/j_acegi_security_check";
-    }
-
     public Authentication attemptAuthentication(HttpServletRequest request)
         throws AuthenticationException {
         String username = obtainUsername(request);
@@ -84,28 +76,23 @@ public class AuthenticationProcessingFilter extends AbstractProcessingFilter {
         setDetails(request, authRequest);
 
         // Place the last username attempted into HttpSession for views
-        request.getSession().setAttribute(ACEGI_SECURITY_LAST_USERNAME_KEY,
-            username);
+        request.getSession()
+               .setAttribute(ACEGI_SECURITY_LAST_USERNAME_KEY, username);
 
         return this.getAuthenticationManager().authenticate(authRequest);
     }
 
-    public void init(FilterConfig filterConfig) throws ServletException {}
-
     /**
-     * Provided so that subclasses may configure what is put into the
-     * authentication request's details property. The default implementation
-     * simply constructs {@link WebAuthenticationDetails}.
+     * This filter by default responds to <code>/j_acegi_security_check</code>.
      *
-     * @param request that an authentication request is being created for
-     * @param authRequest the authentication request object that should have
-     *        its details set
+     * @return the default
      */
-    protected void setDetails(HttpServletRequest request,
-        UsernamePasswordAuthenticationToken authRequest) {
-        authRequest.setDetails(new WebAuthenticationDetails(request, false));
+    public String getDefaultFilterProcessesUrl() {
+        return "/j_acegi_security_check";
     }
 
+    public void init(FilterConfig filterConfig) throws ServletException {}
+
     /**
      * Enables subclasses to override the composition of the password, such as
      * by including additional values and a separator.
@@ -141,4 +128,17 @@ public class AuthenticationProcessingFilter extends AbstractProcessingFilter {
     protected String obtainUsername(HttpServletRequest request) {
         return request.getParameter(ACEGI_SECURITY_FORM_USERNAME_KEY);
     }
+
+    /**
+     * Provided so that subclasses may configure what is put into the
+     * authentication request's details property.
+     *
+     * @param request that an authentication request is being created for
+     * @param authRequest the authentication request object that should have
+     *        its details set
+     */
+    protected void setDetails(HttpServletRequest request,
+        UsernamePasswordAuthenticationToken authRequest) {
+        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
+    }
 }

+ 52 - 35
core/src/main/java/org/acegisecurity/ui/x509/X509ProcessingFilter.java

@@ -1,4 +1,4 @@
-/* Copyright 2004, 2005 Acegi Technology Pty Limited
+/* 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.
@@ -18,19 +18,24 @@ package org.acegisecurity.ui.x509;
 import org.acegisecurity.Authentication;
 import org.acegisecurity.AuthenticationException;
 import org.acegisecurity.AuthenticationManager;
+
 import org.acegisecurity.context.SecurityContextHolder;
+
 import org.acegisecurity.event.authentication.InteractiveAuthenticationSuccessEvent;
+
 import org.acegisecurity.providers.x509.X509AuthenticationToken;
+
 import org.acegisecurity.ui.AbstractProcessingFilter;
-import org.acegisecurity.ui.WebAuthenticationDetails;
+import org.acegisecurity.ui.AuthenticationDetailsSource;
+import org.acegisecurity.ui.AuthenticationDetailsSourceImpl;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import org.springframework.beans.factory.InitializingBean;
 
-import org.springframework.context.ApplicationEventPublisherAware;
 import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ApplicationEventPublisherAware;
 
 import org.springframework.util.Assert;
 
@@ -60,10 +65,11 @@ import javax.servlet.http.HttpServletResponse;
  * 
  * <p>
  * If authentication is successful, an {@link
- * org.acegisecurity.event.authentication.InteractiveAuthenticationSuccessEvent} will be
- * published to the application context. No events will be published if
- * authentication was unsuccessful, because this would generally be recorded
- * via an <code>AuthenticationManager</code>-specific application event.
+ * org.acegisecurity.event.authentication.InteractiveAuthenticationSuccessEvent}
+ * will be published to the application context. No events will be published
+ * if authentication was unsuccessful, because this would generally be
+ * recorded via an <code>AuthenticationManager</code>-specific application
+ * event.
  * </p>
  * 
  * <p>
@@ -76,7 +82,7 @@ import javax.servlet.http.HttpServletResponse;
  * @version $Id$
  */
 public class X509ProcessingFilter implements Filter, InitializingBean,
-        ApplicationEventPublisherAware {
+    ApplicationEventPublisherAware {
     //~ Static fields/initializers =============================================
 
     private static final Log logger = LogFactory.getLog(X509ProcessingFilter.class);
@@ -84,19 +90,11 @@ public class X509ProcessingFilter implements Filter, InitializingBean,
     //~ Instance fields ========================================================
 
     private ApplicationEventPublisher eventPublisher;
+    private AuthenticationDetailsSource authenticationDetailsSource = new AuthenticationDetailsSourceImpl();
     private AuthenticationManager authenticationManager;
 
     //~ Methods ================================================================
 
-    public void setApplicationEventPublisher(ApplicationEventPublisher context) {
-        this.eventPublisher = context;
-    }
-
-    public void setAuthenticationManager(
-        AuthenticationManager authenticationManager) {
-        this.authenticationManager = authenticationManager;
-    }
-
     public void afterPropertiesSet() throws Exception {
         Assert.notNull(authenticationManager,
             "An AuthenticationManager must be set");
@@ -154,7 +152,8 @@ public class X509ProcessingFilter implements Filter, InitializingBean,
             try {
                 X509AuthenticationToken authRequest = new X509AuthenticationToken(clientCertificate);
 
-                authRequest.setDetails(new WebAuthenticationDetails(httpRequest));
+                authRequest.setDetails(authenticationDetailsSource.buildDetails(
+                        (HttpServletRequest) request));
                 authResult = authenticationManager.authenticate(authRequest);
                 successfulAuthentication(httpRequest, httpResponse, authResult);
             } catch (AuthenticationException failed) {
@@ -165,8 +164,39 @@ public class X509ProcessingFilter implements Filter, InitializingBean,
         filterChain.doFilter(request, response);
     }
 
+    private X509Certificate extractClientCertificate(HttpServletRequest request) {
+        X509Certificate[] certs = (X509Certificate[]) request.getAttribute(
+                "javax.servlet.request.X509Certificate");
+
+        if ((certs != null) && (certs.length > 0)) {
+            return certs[0];
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("No client certificate found in request.");
+        }
+
+        return null;
+    }
+
     public void init(FilterConfig ignored) throws ServletException {}
 
+    public void setApplicationEventPublisher(ApplicationEventPublisher context) {
+        this.eventPublisher = context;
+    }
+
+    public void setAuthenticationDetailsSource(
+        AuthenticationDetailsSource authenticationDetailsSource) {
+        Assert.notNull(authenticationDetailsSource,
+            "AuthenticationDetailsSource required");
+        this.authenticationDetailsSource = authenticationDetailsSource;
+    }
+
+    public void setAuthenticationManager(
+        AuthenticationManager authenticationManager) {
+        this.authenticationManager = authenticationManager;
+    }
+
     /**
      * Puts the <code>Authentication</code> instance returned by the
      * authentication manager into the secure context.
@@ -206,25 +236,12 @@ public class X509ProcessingFilter implements Filter, InitializingBean,
         SecurityContextHolder.getContext().setAuthentication(null);
 
         if (logger.isDebugEnabled()) {
-            logger.debug("Updated SecurityContextHolder to contain null Authentication");
+            logger.debug(
+                "Updated SecurityContextHolder to contain null Authentication");
         }
 
-        request.getSession().setAttribute(AbstractProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY,
+        request.getSession()
+               .setAttribute(AbstractProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY,
             failed);
     }
-
-    private X509Certificate extractClientCertificate(HttpServletRequest request) {
-        X509Certificate[] certs = (X509Certificate[]) request.getAttribute(
-                "javax.servlet.request.X509Certificate");
-
-        if ((certs != null) && (certs.length > 0)) {
-            return certs[0];
-        }
-
-        if (logger.isDebugEnabled()) {
-            logger.debug("No client certificate found in request.");
-        }
-
-        return null;
-    }
 }

+ 21 - 18
core/src/test/java/org/acegisecurity/concurrent/ConcurrentSessionControllerImplTests.java

@@ -1,4 +1,4 @@
-/* Copyright 2004, 2005 Acegi Technology Pty Limited
+/* 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.
@@ -18,8 +18,11 @@ package org.acegisecurity.concurrent;
 import junit.framework.TestCase;
 
 import org.acegisecurity.Authentication;
+
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+
 import org.acegisecurity.ui.WebAuthenticationDetails;
+
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpSession;
 
@@ -33,6 +36,23 @@ import org.springframework.mock.web.MockHttpSession;
 public class ConcurrentSessionControllerImplTests extends TestCase {
     //~ Methods ================================================================
 
+    private Authentication createAuthentication(String user, String password) {
+        UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user,
+                password);
+        auth.setDetails(createWebDetails(auth));
+
+        return auth;
+    }
+
+    private WebAuthenticationDetails createWebDetails(Authentication auth) {
+        MockHttpSession session = new MockHttpSession();
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setSession(session);
+        request.setUserPrincipal(auth);
+
+        return new WebAuthenticationDetails(request);
+    }
+
     public void testLifecycle() throws Exception {
         // Build a test fixture
         ConcurrentSessionControllerImpl sc = new ConcurrentSessionControllerImpl();
@@ -103,21 +123,4 @@ public class ConcurrentSessionControllerImplTests extends TestCase {
             assertTrue(true);
         }
     }
-
-    private Authentication createAuthentication(String user, String password) {
-        UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user,
-                password);
-        auth.setDetails(createWebDetails(auth));
-
-        return auth;
-    }
-
-    private WebAuthenticationDetails createWebDetails(Authentication auth) {
-        MockHttpSession session = new MockHttpSession();
-        MockHttpServletRequest request = new MockHttpServletRequest();
-        request.setSession(session);
-        request.setUserPrincipal(auth);
-
-        return new WebAuthenticationDetails(request, false);
-    }
 }