|
|
@@ -43,9 +43,12 @@ import org.springframework.security.SpringSecurityMessageSource;
|
|
|
import org.springframework.security.context.SecurityContextHolder;
|
|
|
import org.springframework.security.event.authentication.AuthenticationSwitchUserEvent;
|
|
|
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
|
|
|
-import org.springframework.security.ui.AbstractProcessingFilter;
|
|
|
import org.springframework.security.ui.AuthenticationDetailsSource;
|
|
|
+import org.springframework.security.ui.AuthenticationFailureHandler;
|
|
|
+import org.springframework.security.ui.AuthenticationSuccessHandler;
|
|
|
import org.springframework.security.ui.FilterChainOrder;
|
|
|
+import org.springframework.security.ui.SimpleUrlAuthenticationFailureHandler;
|
|
|
+import org.springframework.security.ui.SimpleUrlAuthenticationSuccessHandler;
|
|
|
import org.springframework.security.ui.SpringSecurityFilter;
|
|
|
import org.springframework.security.ui.WebAuthenticationDetailsSource;
|
|
|
import org.springframework.security.userdetails.UserDetails;
|
|
|
@@ -53,8 +56,9 @@ import org.springframework.security.userdetails.UserDetailsChecker;
|
|
|
import org.springframework.security.userdetails.UserDetailsService;
|
|
|
import org.springframework.security.userdetails.UsernameNotFoundException;
|
|
|
import org.springframework.security.userdetails.checker.AccountStatusUserDetailsChecker;
|
|
|
-import org.springframework.security.util.RedirectUtils;
|
|
|
+import org.springframework.security.util.UrlUtils;
|
|
|
import org.springframework.util.Assert;
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
|
|
|
|
|
|
/**
|
|
|
@@ -66,12 +70,13 @@ import org.springframework.util.Assert;
|
|
|
* <p>
|
|
|
* This filter assumes that the user performing the switch will be required to be logged in as normal (i.e.
|
|
|
* 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 <code>switchUserUrl</code>). <br>
|
|
|
- * <b>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).</b>
|
|
|
+ * wish to become (see <code>switchUserUrl</code>).
|
|
|
* <p>
|
|
|
- * On successful switch, the user's <code>SecurityContextHolder</code> will be updated to reflect the
|
|
|
- * specified user and will also contain an additinal
|
|
|
+ * <b>Note: This URL will be required to have appropriate security constraints configured so that only users of that
|
|
|
+ * role can access it (e.g. ROLE_ADMIN).</b>
|
|
|
+ * <p>
|
|
|
+ * On a successful switch, the user's <code>SecurityContextHolder</code> will be updated to reflect the
|
|
|
+ * specified user and will also contain an additional
|
|
|
* {@link org.springframework.security.ui.switchuser.SwitchUserGrantedAuthority} which contains the original user.
|
|
|
* <p>
|
|
|
* To 'exit' from a user context, the user will then need to access a URL (see <code>exitUserUrl</code>) that
|
|
|
@@ -80,7 +85,8 @@ import org.springframework.util.Assert;
|
|
|
* To configure the Switch User Processing Filter, create a bean definition for the Switch User processing
|
|
|
* filter and add to the filterChainProxy. Note that the filter must come <b>after</b> the
|
|
|
* <tt>FilterSecurityInteceptor</tt> in the chain, in order to apply the correct constraints to the <tt>switchUserUrl</tt>.
|
|
|
- * Example:<pre>
|
|
|
+ * Example:
|
|
|
+ * <pre>
|
|
|
* <bean id="switchUserProcessingFilter" class="org.springframework.security.ui.switchuser.SwitchUserProcessingFilter">
|
|
|
* <property name="userDetailsService" ref="userDetailsService" />
|
|
|
* <property name="switchUserUrl"><value>/j_spring_security_switch_user</value></property>
|
|
|
@@ -112,63 +118,61 @@ public class SwitchUserProcessingFilter extends SpringSecurityFilter implements
|
|
|
private SwitchUserAuthorityChanger switchUserAuthorityChanger;
|
|
|
private UserDetailsService userDetailsService;
|
|
|
private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
|
|
|
- private boolean useRelativeContext;
|
|
|
+ private AuthenticationSuccessHandler successHandler;
|
|
|
+ private AuthenticationFailureHandler failureHandler;
|
|
|
|
|
|
//~ Methods ========================================================================================================
|
|
|
|
|
|
public void afterPropertiesSet() throws Exception {
|
|
|
- Assert.hasLength(switchUserUrl, "switchUserUrl must be specified");
|
|
|
- Assert.hasLength(exitUserUrl, "exitUserUrl must be specified");
|
|
|
- Assert.hasLength(targetUrl, "targetUrl must be specified");
|
|
|
- Assert.notNull(userDetailsService, "authenticationDao must be specified");
|
|
|
- Assert.notNull(messages, "A message source must be set");
|
|
|
+ Assert.notNull(userDetailsService, "userDetailsService must be specified");
|
|
|
+ Assert.isTrue(successHandler != null || targetUrl != null, "You must set either a successHandler or the targetUrl");
|
|
|
+ if (targetUrl != null) {
|
|
|
+ Assert.isNull(successHandler, "You cannot set both successHandler and targetUrl");
|
|
|
+ successHandler = new SimpleUrlAuthenticationSuccessHandler(targetUrl);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (failureHandler == null) {
|
|
|
+ failureHandler = switchFailureUrl == null ? new SimpleUrlAuthenticationFailureHandler() :
|
|
|
+ new SimpleUrlAuthenticationFailureHandler(switchFailureUrl);
|
|
|
+ } else {
|
|
|
+ Assert.isNull(switchFailureUrl, "You cannot set both a switchFailureUrl and a failureHandler");
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Attempt to exit from an already switched user.
|
|
|
- *
|
|
|
- * @param request The http servlet request
|
|
|
- *
|
|
|
- * @return The original <code>Authentication</code> object or <code>null</code> otherwise.
|
|
|
- *
|
|
|
- * @throws AuthenticationCredentialsNotFoundException If no <code>Authentication</code> associated with this
|
|
|
- * request.
|
|
|
- */
|
|
|
- protected Authentication attemptExitUser(HttpServletRequest request)
|
|
|
- throws AuthenticationCredentialsNotFoundException {
|
|
|
- // need to check to see if the current user has a SwitchUserGrantedAuthority
|
|
|
- Authentication current = SecurityContextHolder.getContext().getAuthentication();
|
|
|
+ public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
|
|
+ throws IOException, ServletException {
|
|
|
|
|
|
- if (null == current) {
|
|
|
- throw new AuthenticationCredentialsNotFoundException(messages.getMessage(
|
|
|
- "SwitchUserProcessingFilter.noCurrentUser", "No current user associated with this request"));
|
|
|
- }
|
|
|
+ // check for switch or exit request
|
|
|
+ if (requiresSwitchUser(request)) {
|
|
|
+ // if set, attempt switch and store original
|
|
|
+ try {
|
|
|
+ Authentication targetUser = attemptSwitchUser(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);
|
|
|
+ // update the current context to the new target user
|
|
|
+ SecurityContextHolder.getContext().setAuthentication(targetUser);
|
|
|
|
|
|
- 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"));
|
|
|
- }
|
|
|
+ // redirect to target url
|
|
|
+ successHandler.onAuthenticationSuccess(request, response, targetUser);
|
|
|
+ } catch (AuthenticationException e) {
|
|
|
+ logger.debug("Switch User failed", e);
|
|
|
+ failureHandler.onAuthenticationFailure(request, response, e);
|
|
|
+ }
|
|
|
|
|
|
- // get the source user details
|
|
|
- UserDetails originalUser = null;
|
|
|
- Object obj = original.getPrincipal();
|
|
|
+ return;
|
|
|
+ } else if (requiresExitUser(request)) {
|
|
|
+ // get the original authentication object (if exists)
|
|
|
+ Authentication originalUser = attemptExitUser(request);
|
|
|
|
|
|
- if ((obj != null) && obj instanceof UserDetails) {
|
|
|
- originalUser = (UserDetails) obj;
|
|
|
- }
|
|
|
+ // update the current context back to the original user
|
|
|
+ SecurityContextHolder.getContext().setAuthentication(originalUser);
|
|
|
|
|
|
- // publish event
|
|
|
- if (this.eventPublisher != null) {
|
|
|
- eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(current, originalUser));
|
|
|
+ // redirect to target url
|
|
|
+ successHandler.onAuthenticationSuccess(request, response, originalUser);
|
|
|
+
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- return original;
|
|
|
+ chain.doFilter(request, response);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -199,7 +203,7 @@ public class SwitchUserProcessingFilter extends SpringSecurityFilter implements
|
|
|
UserDetails targetUser = userDetailsService.loadUserByUsername(username);
|
|
|
userDetailsChecker.check(targetUser);
|
|
|
|
|
|
- // ok, create the switch user token
|
|
|
+ // OK, create the switch user token
|
|
|
targetUserRequest = createSwitchUserToken(request, targetUser);
|
|
|
|
|
|
if (logger.isDebugEnabled()) {
|
|
|
@@ -215,6 +219,53 @@ public class SwitchUserProcessingFilter extends SpringSecurityFilter implements
|
|
|
return targetUserRequest;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Attempt to exit from an already switched user.
|
|
|
+ *
|
|
|
+ * @param request The http servlet request
|
|
|
+ *
|
|
|
+ * @return The original <code>Authentication</code> object or <code>null</code> otherwise.
|
|
|
+ *
|
|
|
+ * @throws AuthenticationCredentialsNotFoundException If no <code>Authentication</code> associated with this
|
|
|
+ * request.
|
|
|
+ */
|
|
|
+ protected Authentication attemptExitUser(HttpServletRequest request)
|
|
|
+ throws AuthenticationCredentialsNotFoundException {
|
|
|
+ // need to check to see if the current user has a SwitchUserGrantedAuthority
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Create a switch user token that contains an additional <tt>GrantedAuthority</tt> that contains the
|
|
|
* original <code>Authentication</code> object.
|
|
|
@@ -257,60 +308,6 @@ public class SwitchUserProcessingFilter extends SpringSecurityFilter implements
|
|
|
return targetUserRequest;
|
|
|
}
|
|
|
|
|
|
- public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
|
|
- throws IOException, ServletException {
|
|
|
-
|
|
|
- // check for switch or exit request
|
|
|
- if (requiresSwitchUser(request)) {
|
|
|
- // if set, attempt switch and store original
|
|
|
-
|
|
|
- try {
|
|
|
- Authentication targetUser = attemptSwitchUser(request);
|
|
|
-
|
|
|
- // 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)) {
|
|
|
- // get the original authentication object (if exists)
|
|
|
- Authentication originalUser = attemptExitUser(request);
|
|
|
-
|
|
|
- // update the current context back to the original user
|
|
|
- SecurityContextHolder.getContext().setAuthentication(originalUser);
|
|
|
-
|
|
|
- // redirect to target url
|
|
|
- sendRedirect(request, response, targetUrl);
|
|
|
-
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- chain.doFilter(request, response);
|
|
|
- }
|
|
|
-
|
|
|
- private void redirectToFailureUrl(HttpServletRequest request, HttpServletResponse response,
|
|
|
- AuthenticationException failed) throws IOException {
|
|
|
- logger.debug("Switch User failed", failed);
|
|
|
-
|
|
|
- if (switchFailureUrl != null) {
|
|
|
- sendRedirect(request, response, switchFailureUrl);
|
|
|
- } else {
|
|
|
- response.getWriter().print("Switch user failed: " + failed.getMessage());
|
|
|
- response.flushBuffer();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- protected void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url)
|
|
|
- throws IOException {
|
|
|
-
|
|
|
- RedirectUtils.sendRedirect(request, response, url, useRelativeContext);
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* 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
|
|
|
@@ -368,7 +365,7 @@ public class SwitchUserProcessingFilter extends SpringSecurityFilter implements
|
|
|
}
|
|
|
|
|
|
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher)
|
|
|
- throws BeansException {
|
|
|
+ throws BeansException {
|
|
|
this.eventPublisher = eventPublisher;
|
|
|
}
|
|
|
|
|
|
@@ -377,30 +374,47 @@ public class SwitchUserProcessingFilter extends SpringSecurityFilter implements
|
|
|
this.authenticationDetailsSource = authenticationDetailsSource;
|
|
|
}
|
|
|
|
|
|
+ public void setMessageSource(MessageSource messageSource) {
|
|
|
+ Assert.notNull(messageSource, "messageSource cannot be null");
|
|
|
+ this.messages = new MessageSourceAccessor(messageSource);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Sets the authentication data access object.
|
|
|
+ *
|
|
|
+ * @param userDetailsService The <tt>UserDetailService</tt> which will be used to load information for the user
|
|
|
+ * that is being switched to.
|
|
|
+ */
|
|
|
+ public void setUserDetailsService(UserDetailsService userDetailsService) {
|
|
|
+ this.userDetailsService = userDetailsService;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Set the URL to respond to exit user processing.
|
|
|
*
|
|
|
* @param exitUserUrl The exit user URL.
|
|
|
*/
|
|
|
public void setExitUserUrl(String exitUserUrl) {
|
|
|
+ Assert.isTrue(UrlUtils.isValidRedirectUrl(exitUserUrl),
|
|
|
+ "exitUserUrl cannot be empty and must be a valid redirect URL");
|
|
|
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) {
|
|
|
+ Assert.isTrue(UrlUtils.isValidRedirectUrl(switchUserUrl),
|
|
|
+ "switchUserUrl cannot be empty and must be a valid redirect URL");
|
|
|
this.switchUserUrl = switchUserUrl;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Sets the URL to go to after a successful switch / exit user request.
|
|
|
+ * Use {@link #setSuccessHandler(AuthenticationSuccessHandler) setSuccessHandler} instead if you need more
|
|
|
+ * customized behaviour.
|
|
|
*
|
|
|
* @param targetUrl The target url.
|
|
|
*/
|
|
|
@@ -409,36 +423,54 @@ public class SwitchUserProcessingFilter extends SpringSecurityFilter implements
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Sets the authentication data access object.
|
|
|
- *
|
|
|
- * @param userDetailsService The authentication dao
|
|
|
- */
|
|
|
- public void setUserDetailsService(UserDetailsService userDetailsService) {
|
|
|
- this.userDetailsService = 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 set to true to exclude the context path from redirect URLs.
|
|
|
+ * Used to define custom behaviour on a successful switch or exit user.
|
|
|
+ * <p>
|
|
|
+ * Can be used instead of setting <tt>targetUrl</tt>.
|
|
|
*/
|
|
|
- public void setUseRelativeContext(boolean useRelativeContext) {
|
|
|
- this.useRelativeContext = useRelativeContext;
|
|
|
+ public void setSuccessHandler(AuthenticationSuccessHandler successHandler) {
|
|
|
+ Assert.notNull(successHandler, "successHandler cannot be null");
|
|
|
+ this.successHandler = successHandler;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Sets the URL to which a user should be redirected if the swittch fails. For example, this might happen because
|
|
|
+ * Sets the URL to which a user should be redirected if the switch 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).
|
|
|
* <p>
|
|
|
- * If not set, an error essage wil be written to the response.
|
|
|
+ * If not set, an error message will be written to the response.
|
|
|
+ * <p>
|
|
|
+ * Use {@link #setFailureHandler(AuthenticationFailureHandler) failureHandler} instead if you need more
|
|
|
+ * customized behaviour.
|
|
|
*
|
|
|
* @param switchFailureUrl the url to redirect to.
|
|
|
*/
|
|
|
public void setSwitchFailureUrl(String switchFailureUrl) {
|
|
|
+ Assert.isTrue(StringUtils.hasText(switchUserUrl) && UrlUtils.isValidRedirectUrl(switchFailureUrl),
|
|
|
+ "switchFailureUrl cannot be empty and must be a valid redirect URL");
|
|
|
this.switchFailureUrl = switchFailureUrl;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Used to define custom behaviour when a switch fails.
|
|
|
+ * <p>
|
|
|
+ * Can be used instead of setting <tt>switchFailureUrl</tt>.
|
|
|
+ */
|
|
|
+ public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
|
|
|
+ Assert.notNull(failureHandler, "failureHandler cannot be null");
|
|
|
+ this.failureHandler = failureHandler;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param switchUserAuthorityChanger to use to fine-tune the authorities granted to subclasses (may be null if
|
|
|
+ * SwitchUserProcessingFilter should not fine-tune the authorities)
|
|
|
+ */
|
|
|
+ public void setSwitchUserAuthorityChanger(SwitchUserAuthorityChanger switchUserAuthorityChanger) {
|
|
|
+ this.switchUserAuthorityChanger = switchUserAuthorityChanger;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setUserDetailsChecker(UserDetailsChecker userDetailsChecker) {
|
|
|
+ this.userDetailsChecker = userDetailsChecker;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Strips any content after the ';' in the request URI
|
|
|
*
|
|
|
@@ -446,7 +478,7 @@ public class SwitchUserProcessingFilter extends SpringSecurityFilter implements
|
|
|
*
|
|
|
* @return The stripped uri
|
|
|
*/
|
|
|
- private static String stripUri(HttpServletRequest request) {
|
|
|
+ private String stripUri(HttpServletRequest request) {
|
|
|
String uri = request.getRequestURI();
|
|
|
int idx = uri.indexOf(';');
|
|
|
|
|
|
@@ -457,14 +489,6 @@ public class SwitchUserProcessingFilter extends SpringSecurityFilter implements
|
|
|
return uri;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * @param switchUserAuthorityChanger to use to fine-tune the authorities granted to subclasses (may be null if
|
|
|
- * SwitchUserProcessingFilter shoudl not fine-tune the authorities)
|
|
|
- */
|
|
|
- public void setSwitchUserAuthorityChanger(SwitchUserAuthorityChanger switchUserAuthorityChanger) {
|
|
|
- this.switchUserAuthorityChanger = switchUserAuthorityChanger;
|
|
|
- }
|
|
|
-
|
|
|
public int getOrder() {
|
|
|
return FilterChainOrder.SWITCH_USER_FILTER;
|
|
|
}
|