From a47a342ce62610178b1af7c89aaaf60c89dda7ed Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 26 Apr 2006 05:24:49 +0000 Subject: [PATCH] SEC-234: Allow pluggable AuthenticationDetailsSource strategy interface. --- .../anonymous/AnonymousProcessingFilter.java | 15 +- .../ui/AbstractProcessingFilter.java | 8 + .../ui/AuthenticationDetailsSource.java | 41 + .../ui/AuthenticationDetailsSourceImpl.java | 69 ++ .../ui/WebAuthenticationDetails.java | 9 +- .../ui/basicauth/BasicProcessingFilter.java | 12 +- .../ui/cas/CasProcessingFilter.java | 3 +- .../ui/digestauth/DigestProcessingFilter.java | 103 ++- .../TokenBasedRememberMeServices.java | 20 +- .../SwitchUserProcessingFilter.java | 750 +++++++++--------- .../AuthenticationProcessingFilter.java | 48 +- .../ui/x509/X509ProcessingFilter.java | 87 +- .../ConcurrentSessionControllerImplTests.java | 39 +- 13 files changed, 683 insertions(+), 521 deletions(-) create mode 100644 core/src/main/java/org/acegisecurity/ui/AuthenticationDetailsSource.java create mode 100644 core/src/main/java/org/acegisecurity/ui/AuthenticationDetailsSourceImpl.java diff --git a/core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousProcessingFilter.java b/core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousProcessingFilter.java index 312c70a27e..fda73d1c54 100644 --- a/core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousProcessingFilter.java +++ b/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; } diff --git a/core/src/main/java/org/acegisecurity/ui/AbstractProcessingFilter.java b/core/src/main/java/org/acegisecurity/ui/AbstractProcessingFilter.java index 09974274c2..fbd29469ac 100644 --- a/core/src/main/java/org/acegisecurity/ui/AbstractProcessingFilter.java +++ b/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; } diff --git a/core/src/main/java/org/acegisecurity/ui/AuthenticationDetailsSource.java b/core/src/main/java/org/acegisecurity/ui/AuthenticationDetailsSource.java new file mode 100644 index 0000000000..2955394c20 --- /dev/null +++ b/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); +} diff --git a/core/src/main/java/org/acegisecurity/ui/AuthenticationDetailsSourceImpl.java b/core/src/main/java/org/acegisecurity/ui/AuthenticationDetailsSourceImpl.java new file mode 100644 index 0000000000..0bfe808e1a --- /dev/null +++ b/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}. + * + *

+ * By default will create an instance of WebAuthenticationDetails. + * Any object that accepts a HttpServletRequest as its sole + * constructor can be used instead of this default. + *

+ * + * @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; + } +} diff --git a/core/src/main/java/org/acegisecurity/ui/WebAuthenticationDetails.java b/core/src/main/java/org/acegisecurity/ui/WebAuthenticationDetails.java index 844267a61a..a51b4a605e 100644 --- a/core/src/main/java/org/acegisecurity/ui/WebAuthenticationDetails.java +++ b/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); diff --git a/core/src/main/java/org/acegisecurity/ui/basicauth/BasicProcessingFilter.java b/core/src/main/java/org/acegisecurity/ui/basicauth/BasicProcessingFilter.java index f652577c68..13eb091558 100644 --- a/core/src/main/java/org/acegisecurity/ui/basicauth/BasicProcessingFilter.java +++ b/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; diff --git a/core/src/main/java/org/acegisecurity/ui/cas/CasProcessingFilter.java b/core/src/main/java/org/acegisecurity/ui/cas/CasProcessingFilter.java index 479edd3ab7..c50b10eb89 100644 --- a/core/src/main/java/org/acegisecurity/ui/cas/CasProcessingFilter.java +++ b/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); } diff --git a/core/src/main/java/org/acegisecurity/ui/digestauth/DigestProcessingFilter.java b/core/src/main/java/org/acegisecurity/ui/digestauth/DigestProcessingFilter.java index 185d735f4a..958771d879 100644 --- a/core/src/main/java/org/acegisecurity/ui/digestauth/DigestProcessingFilter.java +++ b/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,7 +15,42 @@ package org.acegisecurity.ui.digestauth; +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.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; @@ -27,30 +62,6 @@ 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.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; - /** * Processes a HTTP request's Digest authorization headers, putting the result @@ -84,9 +95,9 @@ import org.springframework.util.StringUtils; * *

* 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. *

* @@ -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, * response 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; + } } diff --git a/core/src/main/java/org/acegisecurity/ui/rememberme/TokenBasedRememberMeServices.java b/core/src/main/java/org/acegisecurity/ui/rememberme/TokenBasedRememberMeServices.java index b2d2a4aca0..11b405b020 100644 --- a/core/src/main/java/org/acegisecurity/ui/rememberme/TokenBasedRememberMeServices.java +++ b/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; } diff --git a/core/src/main/java/org/acegisecurity/ui/switchuser/SwitchUserProcessingFilter.java b/core/src/main/java/org/acegisecurity/ui/switchuser/SwitchUserProcessingFilter.java index 4796a635ad..ba58fc9ea1 100644 --- a/core/src/main/java/org/acegisecurity/ui/switchuser/SwitchUserProcessingFilter.java +++ b/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,7 +15,45 @@ package org.acegisecurity.ui.switchuser; +import org.acegisecurity.AccountExpiredException; +import org.acegisecurity.AcegiMessageSource; +import org.acegisecurity.Authentication; +import org.acegisecurity.AuthenticationCredentialsNotFoundException; +import org.acegisecurity.AuthenticationException; +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.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; @@ -29,33 +67,6 @@ 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; -import org.acegisecurity.AuthenticationCredentialsNotFoundException; -import org.acegisecurity.AuthenticationException; -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.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; - /** * Switch User processing filter responsible for user context switching. @@ -104,7 +115,6 @@ import org.springframework.util.Assert; * *

* - * * @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")); - } - - // 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")); - } - - // get the source user details - UserDetails originalUser = null; - Object obj = original.getPrincipal(); - - if ((obj != null) && obj instanceof UserDetails) { - originalUser = (UserDetails) obj; - } - - // publish event - if (this.eventPublisher != null) { - eventPublisher.publishEvent(new AuthenticationSwitchUserEvent( - current, originalUser)); - } - - return original; + if (null == current) { + throw new AuthenticationCredentialsNotFoundException(messages + .getMessage("SwitchUserProcessingFilter.noCurrentUser", + "No current user associated with this request")); } - /** - * 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 Authentication request if - * successfully switched to another user, - * null 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; + // 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); - String username = request.getParameter(ACEGI_SECURITY_SWITCH_USERNAME_KEY); + 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 (username == null) { - username = ""; + // get the source user details + UserDetails originalUser = null; + Object obj = original.getPrincipal(); + + if ((obj != null) && obj instanceof UserDetails) { + originalUser = (UserDetails) obj; + } + + // publish event + if (this.eventPublisher != null) { + eventPublisher.publishEvent(new AuthenticationSwitchUserEvent( + current, originalUser)); + } + + return original; + } + + /** + * 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 Authentication request if successfully + * switched to another user, null 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 = ""; + } + + if (logger.isDebugEnabled()) { + logger.debug("Attempt to switch to user [" + username + "]"); + } + + // load the user by name + UserDetails targetUser = this.userDetailsService.loadUserByUsername(username); + + // user not found + if (targetUser == null) { + throw new UsernameNotFoundException(messages.getMessage( + "SwitchUserProcessingFilter.usernameNotFound", + new Object[] {username}, "Username {0} not found")); + } + + // account is expired + if (!targetUser.isAccountNonLocked()) { + throw new LockedException(messages.getMessage( + "SwitchUserProcessingFilter.locked", + "User account is locked")); + } + + // user is disabled + if (!targetUser.isEnabled()) { + throw new DisabledException(messages.getMessage( + "SwitchUserProcessingFilter.disabled", "User is disabled")); + } + + // account is expired + if (!targetUser.isAccountNonExpired()) { + throw new AccountExpiredException(messages.getMessage( + "SwitchUserProcessingFilter.expired", + "User account has expired")); + } + + // 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); + + if (logger.isDebugEnabled()) { + logger.debug("Switch User Token [" + targetUserRequest + "]"); + } + + // publish event + if (this.eventPublisher != null) { + eventPublisher.publishEvent(new AuthenticationSwitchUserEvent( + SecurityContextHolder.getContext().getAuthentication(), + targetUser)); + } + + return targetUserRequest; + } + + /** + * Create a switch user token that contains an additional + * GrantedAuthority that contains the original + * Authentication 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(authenticationDetailsSource.buildDetails( + (HttpServletRequest) request)); + + return targetUserRequest; + } + + public void destroy() {} + + /** + * @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); + + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + + // check for switch or exit request + if (requiresSwitchUser(httpRequest)) { + // if set, attempt switch and store original + Authentication targetUser = attemptSwitchUser(httpRequest); + + // update the current context to the new target user + SecurityContextHolder.getContext().setAuthentication(targetUser); + + // redirect to target url + httpResponse.sendRedirect(httpResponse.encodeRedirectURL(httpRequest + .getContextPath() + targetUrl)); + + return; + } else if (requiresExitUser(httpRequest)) { + // get the original authentication object (if exists) + Authentication originalUser = attemptExitUser(httpRequest); + + // update the current context back to the original user + SecurityContextHolder.getContext().setAuthentication(originalUser); + + // redirect to target url + httpResponse.sendRedirect(httpResponse.encodeRedirectURL(httpRequest + .getContextPath() + targetUrl)); + + return; + } + + chain.doFilter(request, response); + } + + /** + * Find the original Authentication object from the current + * user's granted authorities. A successfully switched user should have a + * SwitchUserGrantedAuthority that contains the original + * source user Authentication object. + * + * @param current The current Authentication object + * + * @return The source user Authentication object or + * null 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 + "]"); } - - if (logger.isDebugEnabled()) { - logger.debug("Attempt to switch to user [" + username + "]"); - } - - // load the user by name - UserDetails targetUser = this.userDetailsService - .loadUserByUsername(username); - - // user not found - if (targetUser == null) { - throw new UsernameNotFoundException(messages.getMessage( - "SwitchUserProcessingFilter.usernameNotFound", - new Object[] {username}, - "Username {0} not found")); - } - - // account is expired - if (!targetUser.isAccountNonLocked()) { - throw new LockedException(messages.getMessage( - "SwitchUserProcessingFilter.locked", - "User account is locked")); - } - - // user is disabled - if (!targetUser.isEnabled()) { - throw new DisabledException(messages.getMessage( - "SwitchUserProcessingFilter.disabled", - "User is disabled")); - } - - // account is expired - if (!targetUser.isAccountNonExpired()) { - throw new AccountExpiredException(messages.getMessage( - "SwitchUserProcessingFilter.expired", - "User account has expired")); - } - - // 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); - - if (logger.isDebugEnabled()) { - logger.debug("Switch User Token [" + targetUserRequest + "]"); - } - - // publish event - if (this.eventPublisher != null) { - eventPublisher.publishEvent(new AuthenticationSwitchUserEvent( - SecurityContextHolder.getContext().getAuthentication(), - targetUser)); - } - - return targetUserRequest; } - /** - * Create a switch user token that contains an additional - * GrantedAuthority that contains the original - * Authentication 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; + return original; + } - // 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); + public void init(FilterConfig ignored) throws ServletException {} - // get the original authorities - List orig = Arrays.asList(targetUser.getAuthorities()); + /** + * Checks the request URI for the presence of exitUserUrl. + * + * @param request The http servlet request + * + * @return true if the request requires a exit user, + * false otherwise. + * + * @see SwitchUserProcessingFilter#exitUserUrl + */ + protected boolean requiresExitUser(HttpServletRequest request) { + String uri = stripUri(request); - // add the new switch user authority - List newAuths = new ArrayList(orig); - newAuths.add(switchAuthority); + return uri.endsWith(request.getContextPath() + exitUserUrl); + } - GrantedAuthority[] authorities = {}; - authorities = (GrantedAuthority[]) newAuths.toArray(authorities); + /** + * Checks the request URI for the presence of switchUserUrl. + * + * @param request The http servlet request + * + * @return true if the request requires a switch, + * false otherwise. + * + * @see SwitchUserProcessingFilter#switchUserUrl + */ + protected boolean requiresSwitchUser(HttpServletRequest request) { + String uri = stripUri(request); - // create the new authentication token - targetUserRequest = new UsernamePasswordAuthenticationToken(targetUser, - targetUser.getPassword(), authorities); + return uri.endsWith(request.getContextPath() + switchUserUrl); + } - // set details - targetUserRequest.setDetails(new WebAuthenticationDetails( - request, false)); + public void setApplicationEventPublisher( + ApplicationEventPublisher eventPublisher) throws BeansException { + this.eventPublisher = eventPublisher; + } - return targetUserRequest; + public void setAuthenticationDetailsSource( + AuthenticationDetailsSource authenticationDetailsSource) { + Assert.notNull(authenticationDetailsSource, + "AuthenticationDetailsSource required"); + this.authenticationDetailsSource = authenticationDetailsSource; + } + + /** + * 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); } - public void destroy() {} - - /** - * @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); - - HttpServletRequest httpRequest = (HttpServletRequest) request; - HttpServletResponse httpResponse = (HttpServletResponse) response; - - // check for switch or exit request - if (requiresSwitchUser(httpRequest)) { - // if set, attempt switch and store original - Authentication targetUser = attemptSwitchUser(httpRequest); - - // update the current context to the new target user - SecurityContextHolder.getContext() - .setAuthentication(targetUser); - - // redirect to target url - httpResponse.sendRedirect(httpResponse - .encodeRedirectURL(httpRequest - .getContextPath() + targetUrl)); - - return; - } else if (requiresExitUser(httpRequest)) { - // get the original authentication object (if exists) - Authentication originalUser = attemptExitUser(httpRequest); - - // update the current context back to the original user - SecurityContextHolder.getContext() - .setAuthentication(originalUser); - - // redirect to target url - httpResponse.sendRedirect(httpResponse.encodeRedirectURL( - httpRequest.getContextPath() + targetUrl)); - - return; - } - - chain.doFilter(request, response); - } - - /** - * Find the original Authentication object from - * the current user's granted authorities. A successfully switched - * user should have a SwitchUserGrantedAuthority - * that contains the original source user Authentication - * object. - * - * @param current The current Authentication - * object - * - * @return The source user Authentication - * object or null 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 + "]"); - } - } - - return original; - } - - public void init(FilterConfig ignored) - throws ServletException {} - - /** - * Checks the request URI for the presence - * of exitUserUrl. - * - * @param request The http servlet request - * - * @return true if the request requires a exit user, - * false 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 switchUserUrl. - * - * @param request The http servlet request - * - * @return true if the request requires a switch, - * false 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; - } - - /** - * Sets the authentication data access object. - * - * @param authenticationDao The authentication dao - */ - public void setUserDetailsService( - UserDetailsService authenticationDao) { - this.userDetailsService = authenticationDao; - } - - /** - * 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; - } - - /** - * 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; - } + return uri; + } } diff --git a/core/src/main/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilter.java b/core/src/main/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilter.java index 1e8b9e233b..7bf283c11a 100644 --- a/core/src/main/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilter.java +++ b/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 /j_acegi_security_check. - * - * @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 /j_acegi_security_check. * - * @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)); + } } diff --git a/core/src/main/java/org/acegisecurity/ui/x509/X509ProcessingFilter.java b/core/src/main/java/org/acegisecurity/ui/x509/X509ProcessingFilter.java index 7d5a0b88f6..1afaa00e6b 100644 --- a/core/src/main/java/org/acegisecurity/ui/x509/X509ProcessingFilter.java +++ b/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; * *

* 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 AuthenticationManager-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 AuthenticationManager-specific application + * event. *

* *

@@ -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 Authentication 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; - } } diff --git a/core/src/test/java/org/acegisecurity/concurrent/ConcurrentSessionControllerImplTests.java b/core/src/test/java/org/acegisecurity/concurrent/ConcurrentSessionControllerImplTests.java index 89adc33e83..a2af10b2b6 100644 --- a/core/src/test/java/org/acegisecurity/concurrent/ConcurrentSessionControllerImplTests.java +++ b/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); - } }