diff --git a/core/src/main/java/org/springframework/security/ui/switchuser/SwitchUserProcessingFilter.java b/core/src/main/java/org/springframework/security/ui/switchuser/SwitchUserProcessingFilter.java index 7deec4329f..7a86a810bc 100644 --- a/core/src/main/java/org/springframework/security/ui/switchuser/SwitchUserProcessingFilter.java +++ b/core/src/main/java/org/springframework/security/ui/switchuser/SwitchUserProcessingFilter.java @@ -25,21 +25,17 @@ import org.springframework.security.DisabledException; import org.springframework.security.GrantedAuthority; import org.springframework.security.LockedException; import org.springframework.security.util.RedirectUtils; - import org.springframework.security.context.SecurityContextHolder; - import org.springframework.security.event.authentication.AuthenticationSwitchUserEvent; - import org.springframework.security.providers.UsernamePasswordAuthenticationToken; - import org.springframework.security.ui.AuthenticationDetailsSource; import org.springframework.security.ui.AuthenticationDetailsSourceImpl; import org.springframework.security.ui.SpringSecurityFilter; import org.springframework.security.ui.FilterChainOrder; import org.springframework.security.ui.AbstractProcessingFilter; - import org.springframework.security.userdetails.UserDetails; import org.springframework.security.userdetails.UserDetailsService; +import org.springframework.security.userdetails.decorator.StatusCheckingUserDetailsService; import org.springframework.security.userdetails.UsernameNotFoundException; import org.apache.commons.logging.Log; @@ -72,14 +68,14 @@ import javax.servlet.http.HttpServletResponse; * Switch User processing filter responsible for user context switching. *
* This filter is similar to Unix 'su' however for Spring Security-managed web applications. - * A common use-case for this feature is the ability to allow higher-authority users (i.e. ROLE_ADMIN) to switch to a - * regular user (i.e. ROLE_USER). + * A common use-case for this feature is the ability to allow higher-authority users (e.g. ROLE_ADMIN) to switch to a + * regular user (e.g. ROLE_USER). *
* This filter assumes that the user performing the switch will be required to be logged in as normal (i.e.
- * ROLE_ADMIN user). The user will then access a page/controller that enables the administrator to specify who they
+ * as a ROLE_ADMIN user). The user will then access a page/controller that enables the administrator to specify who they
* wish to become (see switchUserUrl
).
- * Note: This URL will be required to have to appropriate security contraints configured so that only users of that
- * role can access (i.e. ROLE_ADMIN).
+ * Note: This URL will be required to have to appropriate security contraints configured so that only users of that
+ * role can access (e.g. ROLE_ADMIN).
*
* On successful switch, the user's SecurityContextHolder
will be updated to reflect the
* specified user and will also contain an additinal
@@ -89,10 +85,11 @@ import javax.servlet.http.HttpServletResponse;
* will switch back to the original user as identified by the SWITCH_USER_GRANTED_AUTHORITY
.
*
* To configure the Switch User Processing Filter, create a bean definition for the Switch User processing
- * filter and add to the filterChainProxy.
+ * filter and add to the filterChainProxy. Note that the filter must come after the
+ * FilterSecurityInteceptor in the chain, in order to apply the correct constraints to the switchUserUrl.
* Example:
* <bean id="switchUserProcessingFilter" class="org.springframework.security.ui.switchuser.SwitchUserProcessingFilter"> - * <property name="authenticationDao" ref="jdbcDaoImpl" /> + * <property name="userDetailsService" ref="userDetailsService" /> * <property name="switchUserUrl"><value>/j_spring_security_switch_user</value></property> * <property name="exitUserUrl"><value>/j_spring_security_exit_user</value></property> * <property name="targetUrl"><value>/index.jsp</value></property></bean> @@ -120,6 +117,7 @@ public class SwitchUserProcessingFilter extends SpringSecurityFilter implements private String exitUserUrl = "/j_spring_security_exit_user"; private String switchUserUrl = "/j_spring_security_switch_user"; private String targetUrl; + private String switchFailureUrl; private SwitchUserAuthorityChanger switchUserAuthorityChanger; private UserDetailsService userDetailsService; private boolean useRelativeContext; @@ -145,7 +143,7 @@ public class SwitchUserProcessingFilter extends SpringSecurityFilter implements * request. */ protected Authentication attemptExitUser(HttpServletRequest request) - throws AuthenticationCredentialsNotFoundException { + throws AuthenticationCredentialsNotFoundException { // need to check to see if the current user has a SwitchUserGrantedAuthority Authentication current = SecurityContextHolder.getContext().getAuthentication(); @@ -184,20 +182,16 @@ public class SwitchUserProcessingFilter extends SpringSecurityFilter implements /** * 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 newAuthentication
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 LockedException if the account is locked. * @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 { + protected Authentication attemptSwitchUser(HttpServletRequest request) throws AuthenticationException { UsernamePasswordAuthenticationToken targetUserRequest = null; String username = request.getParameter(SPRING_SECURITY_SWITCH_USERNAME_KEY); @@ -213,36 +207,8 @@ public class SwitchUserProcessingFilter extends SpringSecurityFilter implements // 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); + targetUserRequest = createSwitchUserToken(request, targetUser); if (logger.isDebugEnabled()) { logger.debug("Switch User Token [" + targetUserRequest + "]"); @@ -262,15 +228,15 @@ public class SwitchUserProcessingFilter extends SpringSecurityFilter implements * originalAuthentication
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) { + private UsernamePasswordAuthenticationToken createSwitchUserToken(HttpServletRequest request, + UserDetails targetUser) { + UsernamePasswordAuthenticationToken targetUserRequest; // grant an additional authority that contains the original Authentication object @@ -297,7 +263,7 @@ public class SwitchUserProcessingFilter extends SpringSecurityFilter implements targetUserRequest = new UsernamePasswordAuthenticationToken(targetUser, targetUser.getPassword(), authorities); // set details - targetUserRequest.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request)); + targetUserRequest.setDetails(authenticationDetailsSource.buildDetails(request)); return targetUserRequest; } @@ -308,13 +274,18 @@ public class SwitchUserProcessingFilter extends SpringSecurityFilter implements // check for switch or exit request if (requiresSwitchUser(request)) { // if set, attempt switch and store original - Authentication targetUser = attemptSwitchUser(request); - // update the current context to the new target user - SecurityContextHolder.getContext().setAuthentication(targetUser); + try { + Authentication targetUser = attemptSwitchUser(request); - // redirect to target url - sendRedirect(request, response, targetUrl); + // update the current context to the new target user + SecurityContextHolder.getContext().setAuthentication(targetUser); + + // redirect to target url + sendRedirect(request, response, targetUrl); + } catch (AuthenticationException e) { + redirectToFailureUrl(request, response, e); + } return; } else if (requiresExitUser(request)) { @@ -333,6 +304,19 @@ public class SwitchUserProcessingFilter extends SpringSecurityFilter implements chain.doFilter(request, response); } + private void redirectToFailureUrl(HttpServletRequest request, HttpServletResponse response, + AuthenticationException failed) throws IOException { + logger.debug("Switch User failed", failed); + + if (switchFailureUrl != null) { + switchFailureUrl = request.getContextPath() + switchFailureUrl; + response.sendRedirect(response.encodeRedirectURL(switchFailureUrl)); + } else { + response.getWriter().print("Switch user failed: " + failed.getMessage()); + response.flushBuffer(); + } + } + protected void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException { @@ -442,19 +426,31 @@ public class SwitchUserProcessingFilter extends SpringSecurityFilter implements * @param userDetailsService The authentication dao */ public void setUserDetailsService(UserDetailsService userDetailsService) { - this.userDetailsService = userDetailsService; + this.userDetailsService = new StatusCheckingUserDetailsService(userDetailsService); } /** * Analogous to the same property in {@link AbstractProcessingFilter}. If set, redirects will * be context-relative (they won't include the context path). * - * @param useRelativeContext + * @param useRelativeContext set to true to exclude the context path from redirect URLs. */ public void setUseRelativeContext(boolean useRelativeContext) { this.useRelativeContext = useRelativeContext; } + /** + * Sets the URL to which a user should be redirected if the swittch fails. For example, this might happen because + * the account they are attempting to switch to is invalid (the user doesn't exist, account is locked etc). + *+ * If not set, an error essage wil be written to the response. + * + * @param switchFailureUrl the url to redirect to. + */ + public void setSwitchFailureUrl(String switchFailureUrl) { + this.switchFailureUrl = switchFailureUrl; + } + /** * Strips any content after the ';' in the request URI * diff --git a/core/src/test/java/org/springframework/security/ui/switchuser/SwitchUserProcessingFilterTests.java b/core/src/test/java/org/springframework/security/ui/switchuser/SwitchUserProcessingFilterTests.java index b859888572..109876b275 100644 --- a/core/src/test/java/org/springframework/security/ui/switchuser/SwitchUserProcessingFilterTests.java +++ b/core/src/test/java/org/springframework/security/ui/switchuser/SwitchUserProcessingFilterTests.java @@ -41,8 +41,10 @@ import org.springframework.dao.DataAccessException; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import javax.servlet.ServletException; import java.util.List; import java.util.ArrayList; +import java.io.IOException; /** @@ -95,8 +97,7 @@ public class SwitchUserProcessingFilterTests extends TestCase { } catch (UsernameNotFoundException expected) {} } - public void testAttemptSwitchToUserThatIsDisabled() - throws Exception { + public void testAttemptSwitchToUserThatIsDisabled() throws Exception { // set current user UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("dano", "hawaii50"); SecurityContextHolder.getContext().setAuthentication(auth); @@ -118,8 +119,7 @@ public class SwitchUserProcessingFilterTests extends TestCase { } } - public void testAttemptSwitchToUserWithAccountExpired() - throws Exception { + public void testAttemptSwitchToUserWithAccountExpired() throws Exception { // set current user UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("dano", "hawaii50"); SecurityContextHolder.getContext().setAuthentication(auth); @@ -141,8 +141,7 @@ public class SwitchUserProcessingFilterTests extends TestCase { } } - public void testAttemptSwitchToUserWithExpiredCredentials() - throws Exception { + public void testAttemptSwitchToUserWithExpiredCredentials() throws Exception { // set current user UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("dano", "hawaii50"); SecurityContextHolder.getContext().setAuthentication(auth); @@ -179,6 +178,31 @@ public class SwitchUserProcessingFilterTests extends TestCase { assertTrue(result != null); } + public void testSwitchToLockedAccountCausesRedirectToSwitchFailureUrl() throws Exception { + UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("dano", "hawaii50"); + SecurityContextHolder.getContext().setAuthentication(auth); + + MockHttpServletRequest request = createMockSwitchRequest(); + request.addParameter(SwitchUserProcessingFilter.SPRING_SECURITY_SWITCH_USERNAME_KEY, "mcgarrett"); + MockHttpServletResponse response = new MockHttpServletResponse(); + SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter(); + filter.setUserDetailsService(new MockAuthenticationDaoUserJackLord()); + + // Check it with no url set + filter.doFilterHttp(request, response, new MockFilterChain(false)); + + assertEquals("Switch user failed: User is disabled", response.getContentAsString()); + + // Now check for the redirect + filter.setSwitchFailureUrl("/switchfailed"); + response = new MockHttpServletResponse(); + + filter.doFilterHttp(request, response, new MockFilterChain(false)); + + assertEquals("/switchfailed", response.getRedirectedUrl()); + + } + public void testIfSwitchUserWithNullUsernameThrowsException() throws Exception { // set current user UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("dano", "hawaii50");