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 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 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
* 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) {
+ 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");