diff --git a/core/src/main/java/org/springframework/security/config/FormLoginBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/FormLoginBeanDefinitionParser.java index 06a8bad3f8..622640c0be 100644 --- a/core/src/main/java/org/springframework/security/config/FormLoginBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/FormLoginBeanDefinitionParser.java @@ -7,6 +7,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.security.ui.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint; import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter; import org.springframework.util.StringUtils; @@ -23,24 +24,26 @@ import org.apache.commons.logging.LogFactory; public class FormLoginBeanDefinitionParser implements BeanDefinitionParser { protected final Log logger = LogFactory.getLog(getClass()); - static final String ATT_LOGIN_URL = "login-processing-url"; + private static final String ATT_LOGIN_URL = "login-processing-url"; static final String ATT_LOGIN_PAGE = "login-page"; - static final String DEF_LOGIN_PAGE = DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL; + private static final String DEF_LOGIN_PAGE = DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL; - static final String ATT_FORM_LOGIN_TARGET_URL = "default-target-url"; - static final String ATT_ALWAYS_USE_DEFAULT_TARGET_URL = "always-use-default-target"; - static final String DEF_FORM_LOGIN_TARGET_URL = "/"; + private static final String ATT_FORM_LOGIN_TARGET_URL = "default-target-url"; + private static final String ATT_ALWAYS_USE_DEFAULT_TARGET_URL = "always-use-default-target"; + private static final String DEF_FORM_LOGIN_TARGET_URL = "/"; - static final String ATT_FORM_LOGIN_AUTHENTICATION_FAILURE_URL = "authentication-failure-url"; - static final String DEF_FORM_LOGIN_AUTHENTICATION_FAILURE_URL = DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL + "?" + DefaultLoginPageGeneratingFilter.ERROR_PARAMETER_NAME; + private static final String ATT_FORM_LOGIN_AUTHENTICATION_FAILURE_URL = "authentication-failure-url"; + private static final String DEF_FORM_LOGIN_AUTHENTICATION_FAILURE_URL = DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL + "?" + DefaultLoginPageGeneratingFilter.ERROR_PARAMETER_NAME; - String defaultLoginProcessingUrl; - String filterClassName; + private static final String ATT_SUCCESS_HANDLER_REF = "authentication-success-handler-ref"; - RootBeanDefinition filterBean; - RootBeanDefinition entryPointBean; - String loginPage; + private String defaultLoginProcessingUrl; + private String filterClassName; + + private RootBeanDefinition filterBean; + private RootBeanDefinition entryPointBean; + private String loginPage; FormLoginBeanDefinitionParser(String defaultLoginProcessingUrl, String filterClassName) { this.defaultLoginProcessingUrl = defaultLoginProcessingUrl; @@ -52,6 +55,7 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser { String defaultTargetUrl = null; String authenticationFailureUrl = null; String alwaysUseDefault = null; + String successHandlerRef = null; Object source = null; @@ -77,6 +81,7 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser { ConfigUtils.validateHttpRedirect(authenticationFailureUrl, pc, source); alwaysUseDefault = elt.getAttribute(ATT_ALWAYS_USE_DEFAULT_TARGET_URL); loginPage = elt.getAttribute(ATT_LOGIN_PAGE); + successHandlerRef = elt.getAttribute(ATT_SUCCESS_HANDLER_REF); if (!StringUtils.hasText(loginPage)) { loginPage = null; @@ -87,7 +92,8 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser { ConfigUtils.registerProviderManagerIfNecessary(pc); - filterBean = createFilterBean(loginUrl, defaultTargetUrl, alwaysUseDefault, loginPage, authenticationFailureUrl); + filterBean = createFilterBean(loginUrl, defaultTargetUrl, alwaysUseDefault, loginPage, authenticationFailureUrl, + successHandlerRef); filterBean.setSource(source); filterBean.getPropertyValues().addPropertyValue("authenticationManager", new RuntimeBeanReference(BeanIds.AUTHENTICATION_MANAGER)); @@ -117,7 +123,7 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser { } private RootBeanDefinition createFilterBean(String loginUrl, String defaultTargetUrl, String alwaysUseDefault, - String loginPage, String authenticationFailureUrl) { + String loginPage, String authenticationFailureUrl, String successHandlerRef) { BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.rootBeanDefinition(filterClassName); @@ -125,18 +131,19 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser { loginUrl = defaultLoginProcessingUrl; } - if ("true".equals(alwaysUseDefault)) { - filterBuilder.addPropertyValue("alwaysUseDefaultTargetUrl", Boolean.TRUE); - } - filterBuilder.addPropertyValue("filterProcessesUrl", loginUrl); - if (!StringUtils.hasText(defaultTargetUrl)) { - defaultTargetUrl = DEF_FORM_LOGIN_TARGET_URL; + if (StringUtils.hasText(successHandlerRef)) { + filterBuilder.addPropertyReference("successHandler", successHandlerRef); + } else { + BeanDefinitionBuilder successHandler = BeanDefinitionBuilder.rootBeanDefinition(SavedRequestAwareAuthenticationSuccessHandler.class); + if ("true".equals(alwaysUseDefault)) { + successHandler.addPropertyValue("alwaysUseDefaultTargetUrl", Boolean.TRUE); + } + successHandler.addPropertyValue("defaultTargetUrl", StringUtils.hasText(defaultTargetUrl) ? defaultTargetUrl : DEF_FORM_LOGIN_TARGET_URL); + filterBuilder.addPropertyValue("successHandler", successHandler.getBeanDefinition()); } - filterBuilder.addPropertyValue("defaultTargetUrl", defaultTargetUrl); - if (!StringUtils.hasText(authenticationFailureUrl)) { // Fallback to redisplaying the custom login page, if one was specified if (StringUtils.hasText(loginPage)) { diff --git a/core/src/main/java/org/springframework/security/ui/AbstractProcessingFilter.java b/core/src/main/java/org/springframework/security/ui/AbstractProcessingFilter.java index 5d67feb6a2..5c487839c5 100644 --- a/core/src/main/java/org/springframework/security/ui/AbstractProcessingFilter.java +++ b/core/src/main/java/org/springframework/security/ui/AbstractProcessingFilter.java @@ -66,15 +66,6 @@ import javax.servlet.http.HttpSession; *
* To use this filter, it is necessary to specify the following properties: *
defaultTargetUrl indicates the URL that should be used
- * for redirection if the HttpSession attribute named
- * {@link SavedRequest#SPRING_SECURITY_SAVED_REQUEST_KEY} does not indicate the target URL once
- * authentication is completed successfully. eg: /. The
- * defaultTargetUrl will be treated as relative to the web-app's
- * context path, and should include the leading /.
- * Alternatively, inclusion of a scheme name (eg http:// or https://) as the
- * prefix will denote a fully-qualified URL and this is also supported. More
- * complex behaviour can be implemented by using a customised {@link TargetUrlResolver}.authenticationFailureUrl (optional) indicates the URL that should be
* used for redirection if the authentication request fails. eg:
* /login.jsp?login_error=1. If not configured, sendError will be
@@ -152,31 +143,15 @@ public abstract class AbstractProcessingFilter extends SpringSecurityFilter impl
*/
private RememberMeServices rememberMeServices = null;
- private TargetUrlResolver targetUrlResolver = new TargetUrlResolverImpl();
-
/** Where to redirect the browser to if authentication fails */
private String authenticationFailureUrl;
- /**
- * Where to redirect the browser to if authentication is successful and no saved request is stored
- * in the session.
- */
- private String defaultTargetUrl;
-
/**
* The URL destination that this filter intercepts and processes (usually
* something like /j_spring_security_check)
*/
private String filterProcessesUrl = getDefaultFilterProcessesUrl();
- /**
- * If true, will always redirect to the value of
- * {@link #getDefaultTargetUrl} upon successful authentication, irrespective
- * of the page that caused the authentication request (defaults to
- * false).
- */
- private boolean alwaysUseDefaultTargetUrl = false;
-
/**
* Indicates if the filter chain should be continued prior to delegation to
* {@link #successfulAuthentication(HttpServletRequest, HttpServletResponse,
@@ -189,7 +164,7 @@ public abstract class AbstractProcessingFilter extends SpringSecurityFilter impl
* If true, causes any redirection URLs to be calculated minus the protocol
* and context path (defaults to false).
*/
- private boolean useRelativeContext = false;
+ protected boolean useRelativeContext = false;
/**
@@ -203,7 +178,7 @@ public abstract class AbstractProcessingFilter extends SpringSecurityFilter impl
* If {@link #invalidateSessionOnSuccessfulAuthentication} is true, this
* flag indicates that the session attributes of the session to be invalidated
* are to be migrated to the new session. Defaults to true since
- * nothing will happpen unless {@link #invalidateSessionOnSuccessfulAuthentication}
+ * nothing will happen unless {@link #invalidateSessionOnSuccessfulAuthentication}
* is true.
*/
private boolean migrateInvalidatedSessionAttributes = true;
@@ -214,16 +189,16 @@ public abstract class AbstractProcessingFilter extends SpringSecurityFilter impl
private SessionRegistry sessionRegistry;
+ private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
+ private AuthenticationFailureHandler failureHandler = null;
+
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.hasLength(filterProcessesUrl, "filterProcessesUrl must be specified");
Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid redirect URL");
- Assert.hasLength(defaultTargetUrl, "defaultTargetUrl must be specified");
- Assert.isTrue(UrlUtils.isValidRedirectUrl(defaultTargetUrl), defaultTargetUrl + " isn't a valid redirect URL");
Assert.isTrue(UrlUtils.isValidRedirectUrl(authenticationFailureUrl), authenticationFailureUrl + " isn't a valid redirect URL");
Assert.notNull(authenticationManager, "authenticationManager must be specified");
- Assert.notNull(targetUrlResolver, "targetUrlResolver cannot be null");
if (rememberMeServices == null) {
rememberMeServices = new NullRememberMeServices();
@@ -245,45 +220,41 @@ public abstract class AbstractProcessingFilter extends SpringSecurityFilter impl
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException,
ServletException {
- if (requiresAuthentication(request, response)) {
- if (logger.isDebugEnabled()) {
- logger.debug("Request is to process authentication");
- }
-
- Authentication authResult;
-
- try {
- onPreAuthentication(request, response);
- authResult = attemptAuthentication(request);
- }
- catch (AuthenticationException failed) {
- // Authentication failed
- unsuccessfulAuthentication(request, response, failed);
-
- return;
- }
-
- // Authentication success
- if (continueChainBeforeSuccessfulAuthentication) {
- chain.doFilter(request, response);
- }
-
- successfulAuthentication(request, response, authResult);
+ if (!requiresAuthentication(request, response)) {
+ chain.doFilter(request, response);
return;
}
- chain.doFilter(request, response);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Request is to process authentication");
+ }
+
+ Authentication authResult;
+
+ try {
+ onPreAuthentication(request, response);
+ authResult = attemptAuthentication(request);
+ }
+ catch (AuthenticationException failed) {
+ // Authentication failed
+ unsuccessfulAuthentication(request, response, failed);
+
+ return;
+ }
+
+ // Authentication success
+ if (continueChainBeforeSuccessfulAuthentication) {
+ chain.doFilter(request, response);
+ }
+
+ successfulAuthentication(request, response, authResult);
}
protected void onPreAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException {
}
- protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
- Authentication authResult) throws IOException {
- }
-
protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException {
}
@@ -326,35 +297,20 @@ public abstract class AbstractProcessingFilter extends SpringSecurityFilter impl
return uri.endsWith(request.getContextPath() + filterProcessesUrl);
}
- protected void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url)
- throws IOException {
-
- RedirectUtils.sendRedirect(request, response, url, useRelativeContext);
- }
-
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
Authentication authResult) throws IOException, ServletException {
+
if (logger.isDebugEnabled()) {
- logger.debug("Authentication success: " + authResult.toString());
+ logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
- if (logger.isDebugEnabled()) {
- logger.debug("Updated SecurityContextHolder to contain the following Authentication: '" + authResult + "'");
- }
-
if (invalidateSessionOnSuccessfulAuthentication) {
SessionUtils.startNewSessionIfRequired(request, migrateInvalidatedSessionAttributes, sessionRegistry);
}
- String targetUrl = determineTargetUrl(request);
-
- if (logger.isDebugEnabled()) {
- logger.debug("Redirecting to target URL from HTTP Session (or default): " + targetUrl);
- }
-
- onSuccessfulAuthentication(request, response, authResult);
+ successHandler.onAuthenticationSuccess(request, response, authResult);
rememberMeServices.loginSuccess(request, response, authResult);
@@ -362,20 +318,6 @@ public abstract class AbstractProcessingFilter extends SpringSecurityFilter impl
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
-
- sendRedirect(request, response, targetUrl);
- }
-
- protected String determineTargetUrl(HttpServletRequest request) {
- // Don't attempt to obtain the url from the saved request if alwaysUsedefaultTargetUrl is set
- String targetUrl = alwaysUseDefaultTargetUrl ? null :
- targetUrlResolver.determineTargetUrl(request, SecurityContextHolder.getContext().getAuthentication());
-
- if (targetUrl == null) {
- targetUrl = getDefaultTargetUrl();
- }
-
- return targetUrl;
}
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
@@ -411,7 +353,7 @@ public abstract class AbstractProcessingFilter extends SpringSecurityFilter impl
} else if (serverSideRedirect){
request.getRequestDispatcher(failureUrl).forward(request, response);
} else {
- sendRedirect(request, response, failureUrl);
+ RedirectUtils.sendRedirect(request, response, failureUrl, useRelativeContext);
}
}
@@ -443,25 +385,6 @@ public abstract class AbstractProcessingFilter extends SpringSecurityFilter impl
*/
public abstract String getDefaultFilterProcessesUrl();
- /**
- * Supplies the default target Url that will be used if no saved request is
- * found or the alwaysUseDefaultTargetUrl propert is set to true.
- * Override this method of you want to provide a customized default Url (for
- * example if you want different Urls depending on the authorities of the
- * user who has just logged in).
- *
- * @return the defaultTargetUrl property
- */
- public String getDefaultTargetUrl() {
- return defaultTargetUrl;
- }
-
- public void setDefaultTargetUrl(String defaultTargetUrl) {
- Assert.isTrue(defaultTargetUrl.startsWith("/") | defaultTargetUrl.startsWith("http"),
- "defaultTarget must start with '/' or with 'http(s)'");
- this.defaultTargetUrl = defaultTargetUrl;
- }
-
protected Properties getExceptionMappings() {
return new Properties(exceptionMappings);
}
@@ -486,14 +409,6 @@ public abstract class AbstractProcessingFilter extends SpringSecurityFilter impl
this.rememberMeServices = rememberMeServices;
}
- boolean isAlwaysUseDefaultTargetUrl() {
- return alwaysUseDefaultTargetUrl;
- }
-
- public void setAlwaysUseDefaultTargetUrl(boolean alwaysUseDefaultTargetUrl) {
- this.alwaysUseDefaultTargetUrl = alwaysUseDefaultTargetUrl;
- }
-
public void setContinueChainBeforeSuccessfulAuthentication(boolean continueChainBeforeSuccessfulAuthentication) {
this.continueChainBeforeSuccessfulAuthentication = continueChainBeforeSuccessfulAuthentication;
}
@@ -536,20 +451,6 @@ public abstract class AbstractProcessingFilter extends SpringSecurityFilter impl
this.allowSessionCreation = allowSessionCreation;
}
- /**
- * @return the targetUrlResolver
- */
- protected TargetUrlResolver getTargetUrlResolver() {
- return targetUrlResolver;
- }
-
- /**
- * @param targetUrlResolver the targetUrlResolver to set
- */
- public void setTargetUrlResolver(TargetUrlResolver targetUrlResolver) {
- this.targetUrlResolver = targetUrlResolver;
- }
-
/**
* Tells if we are to do a server side include of the error URL instead of a 302 redirect.
*
@@ -566,4 +467,12 @@ public abstract class AbstractProcessingFilter extends SpringSecurityFilter impl
public void setSessionRegistry(SessionRegistry sessionRegistry) {
this.sessionRegistry = sessionRegistry;
}
+
+ public void setSuccessHandler(AuthenticationSuccessHandler successHandler) {
+ this.successHandler = successHandler;
+ }
+
+ public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
+ this.failureHandler = failureHandler;
+ }
}
diff --git a/core/src/main/java/org/springframework/security/ui/AuthenticationSuccessHandler.java b/core/src/main/java/org/springframework/security/ui/AuthenticationSuccessHandler.java
index 2ccb04f0fe..582cbcf4b3 100644
--- a/core/src/main/java/org/springframework/security/ui/AuthenticationSuccessHandler.java
+++ b/core/src/main/java/org/springframework/security/ui/AuthenticationSuccessHandler.java
@@ -1,5 +1,8 @@
package org.springframework.security.ui;
+import java.io.IOException;
+
+import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -27,6 +30,7 @@ public interface AuthenticationSuccessHandler {
* @param response the response
* @param authentication the Authentication object which was created during the authentication process.
*/
- void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication);
+ void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
+ Authentication authentication) throws IOException, ServletException;
}
diff --git a/core/src/main/java/org/springframework/security/ui/SavedRequestAwareAuthenticationSuccessHandler.java b/core/src/main/java/org/springframework/security/ui/SavedRequestAwareAuthenticationSuccessHandler.java
new file mode 100644
index 0000000000..be2bde2bdc
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/ui/SavedRequestAwareAuthenticationSuccessHandler.java
@@ -0,0 +1,219 @@
+package org.springframework.security.ui;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.security.Authentication;
+import org.springframework.security.ui.savedrequest.SavedRequest;
+import org.springframework.security.util.RedirectUtils;
+import org.springframework.security.wrapper.SavedRequestAwareWrapper;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Decides on the redirect destination following a successful authentication, based on the following
+ * configuration options:
+ *
+ * true, will only use SavedRequest to determine the target URL on successful
+ * authentication if the request that caused the authentication request was a GET.
+ * It will then return null for a POST/PUT request.
+ * Defaults to false.
+ */
+ private boolean justUseSavedRequestOnGet = false;
+
+ private String defaultTargetUrl = "/";
+
+ /**
+ * If true, will always redirect to the value of
+ * {@link #getDefaultTargetUrl} upon successful authentication, irrespective
+ * of the page that caused the authentication request (defaults to
+ * false).
+ */
+ private boolean alwaysUseDefaultTargetUrl = false;
+
+ /**
+ * If true, causes any redirection URLs to be calculated minus the protocol
+ * and context path (defaults to false).
+ */
+ private boolean useRelativeContext = false;
+
+ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
+ Authentication authentication) throws ServletException, IOException {
+
+ if (alwaysUseDefaultTargetUrl) {
+ removeSavedRequest(request);
+ RedirectUtils.sendRedirect(request, response, defaultTargetUrl, useRelativeContext);
+ return;
+ }
+
+ // Check for the parameter and use that if available
+ String targetUrl = request.getParameter(targetUrlParameter);
+
+ if (StringUtils.hasText(targetUrl)) {
+ try {
+ targetUrl = URLDecoder.decode(targetUrl, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("UTF-8 not supported. Shouldn't be possible");
+ }
+
+ logger.debug("Found targetUrlParameter in request. Redirecting to: " + targetUrl);
+
+ removeSavedRequest(request);
+ RedirectUtils.sendRedirect(request, response, targetUrl, useRelativeContext);
+
+ return;
+ }
+
+ // Try the SavedRequest URL
+ SavedRequest savedRequest = getSavedRequest(request);
+
+ if (savedRequest != null) {
+ if (!justUseSavedRequestOnGet || savedRequest.getMethod().equals("GET")) {
+ targetUrl = savedRequest.getFullRequestUrl();
+ logger.debug("Redirecting to SavedRequest Url: " + targetUrl);
+ } else {
+ removeSavedRequest(request);
+ }
+ }
+
+ if (targetUrl == null) {
+ targetUrl = defaultTargetUrl;
+ logger.debug("Redirecting to default Url: " + targetUrl);
+ }
+
+ RedirectUtils.sendRedirect(request, response, targetUrl, useRelativeContext);
+
+ }
+
+ private SavedRequest getSavedRequest(HttpServletRequest request) {
+ HttpSession session = request.getSession(false);
+
+ if (session != null) {
+ return (SavedRequest) session.getAttribute(SavedRequest.SPRING_SECURITY_SAVED_REQUEST_KEY);
+ }
+
+ return null;
+ }
+
+ private void removeSavedRequest(HttpServletRequest request) {
+ HttpSession session = request.getSession(false);
+
+ if (session != null) {
+ logger.debug("Removing SavedRequest from session if present");
+ session.removeAttribute(SavedRequest.SPRING_SECURITY_SAVED_REQUEST_KEY);
+ }
+ }
+
+ /**
+ * Supplies the default target Url that will be used if no saved request is found or the
+ * alwaysUseDefaultTargetUrl property is set to true. If not set, defaults to /.
+ *
+ * @return the defaultTargetUrl property
+ */
+ protected String getDefaultTargetUrl() {
+ return defaultTargetUrl;
+ }
+
+ /**
+ * Supplies the default target Url that will be used if no saved request is found in the session, or the
+ * alwaysUseDefaultTargetUrl property is set to true. If not set, defaults to /. It
+ * will be treated as relative to the web-app's context path, and should include the leading /.
+ * Alternatively, inclusion of a scheme name (such as "http://" or "https://") as the prefix will denote a
+ * fully-qualified URL and this is also supported.
+ *
+ * @param defaultTargetUrl
+ */
+ public void setDefaultTargetUrl(String defaultTargetUrl) {
+ Assert.isTrue(defaultTargetUrl.startsWith("/") | defaultTargetUrl.startsWith("http"),
+ "defaultTarget must start with '/' or with 'http(s)'");
+ this.defaultTargetUrl = defaultTargetUrl;
+ }
+
+ /**
+ * If true, will always redirect to the value of defaultTargetUrl
+ * (defaults to false).
+ */
+ public void setAlwaysUseDefaultTargetUrl(boolean alwaysUseDefaultTargetUrl) {
+ this.alwaysUseDefaultTargetUrl = alwaysUseDefaultTargetUrl;
+ }
+
+ boolean isAlwaysUseDefaultTargetUrl() {
+ return alwaysUseDefaultTargetUrl;
+ }
+
+ /**
+ * @return true if just GET request will be used
+ * to determine target URLs, false otherwise.
+ */
+ protected boolean isJustUseSavedRequestOnGet() {
+ return justUseSavedRequestOnGet;
+ }
+
+ /**
+ * @param justUseSavedRequestOnGet set to true if
+ * just GET request will be used to determine target URLs,
+ * false otherwise.
+ */
+ public void setJustUseSavedRequestOnGet(boolean justUseSavedRequestOnGet) {
+ this.justUseSavedRequestOnGet = justUseSavedRequestOnGet;
+ }
+
+ /**
+ * Before checking the SavedRequest, the current request will be checked for this parameter
+ * and the value used as the target URL if resent.
+ *
+ * @param targetUrlParameter the name of the parameter containing the encoded target URL. Defaults
+ * to "redirect".
+ */
+ public void setTargetUrlParameter(String targetUrlParameter) {
+ Assert.hasText("targetUrlParamete canot be null or empty");
+ this.targetUrlParameter = targetUrlParameter;
+ }
+
+ void setUseRelativeContext(boolean useRelativeContext) {
+ this.useRelativeContext = useRelativeContext;
+ }
+}
diff --git a/core/src/main/java/org/springframework/security/ui/TargetUrlResolver.java b/core/src/main/java/org/springframework/security/ui/TargetUrlResolver.java
deleted file mode 100644
index 30e9bfad29..0000000000
--- a/core/src/main/java/org/springframework/security/ui/TargetUrlResolver.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/* 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.springframework.security.ui;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.springframework.security.Authentication;
-
-/**
- * Used by {@link AbstractProcessingFilter} to determine target URL in case of
- * successful authentication.
- *
- * @author Martino Piccinato
- * @version $Id$
- * @since 2.0
- *
- */
-public interface TargetUrlResolver {
-
- /**
- * @param currentRequest the current request
- * @param auth The authentication token generated after successful authentication
- * @return The URL to be used
- */
- public String determineTargetUrl(HttpServletRequest currentRequest, Authentication auth);
-
-}
diff --git a/core/src/main/java/org/springframework/security/ui/TargetUrlResolverImpl.java b/core/src/main/java/org/springframework/security/ui/TargetUrlResolverImpl.java
deleted file mode 100644
index 5ddc995563..0000000000
--- a/core/src/main/java/org/springframework/security/ui/TargetUrlResolverImpl.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/* 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.springframework.security.ui;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLDecoder;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSession;
-
-import org.springframework.security.Authentication;
-import org.springframework.security.ui.savedrequest.SavedRequest;
-import org.springframework.util.Assert;
-import org.springframework.util.StringUtils;
-
-
-/**
- * Default implementation for {@link TargetUrlResolver}
- *
- * Returns a target URL based from the contents of the configured targetUrlParameter if present on
- * the current request. Failing that, the SavedRequest in the session will be used.
- *
- * @author Martino Piccinato
- * @author Luke Taylor
- * @version $Id$
- * @since 2.0
- *
- */
-public class TargetUrlResolverImpl implements TargetUrlResolver {
- public static String DEFAULT_TARGET_PARAMETER = "spring-security-redirect";
-
- /* SEC-213 */
- private String targetUrlParameter = DEFAULT_TARGET_PARAMETER;
-
- /**
- * If true, will only use SavedRequest to determine the target URL on successful
- * authentication if the request that caused the authentication request was a GET.
- * It will then return null for a POST/PUT request.
- * Defaults to false.
- */
- private boolean justUseSavedRequestOnGet = false;
-
- public String determineTargetUrl(HttpServletRequest currentRequest, Authentication auth) {
-
- String targetUrl = currentRequest.getParameter(targetUrlParameter);
-
- if (StringUtils.hasText(targetUrl)) {
- try {
- return URLDecoder.decode(targetUrl, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- throw new IllegalStateException("UTF-8 not supported. Shouldn't be possible");
- }
- }
-
- SavedRequest savedRequest = getSavedRequest(currentRequest);
-
- if (savedRequest != null) {
- if (!justUseSavedRequestOnGet || savedRequest.getMethod().equals("GET")) {
- targetUrl = savedRequest.getFullRequestUrl();
- }
- }
-
- return targetUrl;
- }
-
- private static SavedRequest getSavedRequest(HttpServletRequest request) {
- HttpSession session = request.getSession(false);
-
- if (session == null) {
- return null;
- }
-
- SavedRequest savedRequest = (SavedRequest) session.getAttribute(SavedRequest.SPRING_SECURITY_SAVED_REQUEST_KEY);
-
- return savedRequest;
- }
-
- /**
- * @return true if just GET request will be used
- * to determine target URLs, false otherwise.
- */
- protected boolean isJustUseSavedRequestOnGet() {
- return justUseSavedRequestOnGet;
- }
-
- /**
- * @param justUseSavedRequestOnGet set to true if
- * just GET request will be used to determine target URLs,
- * false otherwise.
- */
- public void setJustUseSavedRequestOnGet(boolean justUseSavedRequestOnGet) {
- this.justUseSavedRequestOnGet = justUseSavedRequestOnGet;
- }
-
-
- /**
- * Before checking the SavedRequest, the current request will be checked for this parameter
- * and the value used as the target URL if resent.
- *
- * @param targetUrlParameter the name of the parameter containing the encoded target URL. Defaults
- * to "redirect".
- */
- public void setTargetUrlParameter(String targetUrlParameter) {
- Assert.hasText("targetUrlParamete canot be null or empty");
- this.targetUrlParameter = targetUrlParameter;
- }
-}
diff --git a/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java b/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java
index 5348db328b..fbfae66c18 100644
--- a/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java
+++ b/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java
@@ -175,8 +175,8 @@ public class HttpSecurityBeanDefinitionParserTests {
"" + AUTH_PROVIDER_XML);
// These will be matched by the default pattern "/**"
AuthenticationProcessingFilter filter = (AuthenticationProcessingFilter) getFilters("/anything").get(1);
- assertEquals("/default", filter.getDefaultTargetUrl());
- assertEquals(Boolean.TRUE, FieldUtils.getFieldValue(filter, "alwaysUseDefaultTargetUrl"));
+ assertEquals("/default", FieldUtils.getFieldValue(filter, "successHandler.defaultTargetUrl"));
+ assertEquals(Boolean.TRUE, FieldUtils.getFieldValue(filter, "successHandler.alwaysUseDefaultTargetUrl"));
}
@Test(expected=BeanCreationException.class)
diff --git a/core/src/test/java/org/springframework/security/ui/AbstractProcessingFilterTests.java b/core/src/test/java/org/springframework/security/ui/AbstractProcessingFilterTests.java
index ebc85bc878..49f68bcfa1 100644
--- a/core/src/test/java/org/springframework/security/ui/AbstractProcessingFilterTests.java
+++ b/core/src/test/java/org/springframework/security/ui/AbstractProcessingFilterTests.java
@@ -54,15 +54,7 @@ import org.springframework.security.util.PortResolverImpl;
* @version $Id$
*/
public class AbstractProcessingFilterTests extends TestCase {
- //~ Constructors ===================================================================================================
-
- public AbstractProcessingFilterTests() {
- }
-
- public AbstractProcessingFilterTests(String arg0) {
- super(arg0);
- }
-
+ SavedRequestAwareAuthenticationSuccessHandler successHandler;
//~ Methods ========================================================================================================
private MockHttpServletRequest createMockRequest() {
@@ -108,6 +100,8 @@ public class AbstractProcessingFilterTests extends TestCase {
protected void setUp() throws Exception {
super.setUp();
+ successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
+ successHandler.setDefaultTargetUrl("/logged_in.jsp");
SecurityContextHolder.clearContext();
}
@@ -179,7 +173,7 @@ public class AbstractProcessingFilterTests extends TestCase {
// Setup our test object, to grant access
MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true);
filter.setFilterProcessesUrl("/j_OTHER_LOCATION");
- filter.setDefaultTargetUrl("/logged_in.jsp");
+ filter.setSuccessHandler(successHandler);
// Test
executeFilterInContainerSimulator(config, filter, request, response, chain);
@@ -191,7 +185,6 @@ public class AbstractProcessingFilterTests extends TestCase {
public void testGettersSetters() throws Exception {
AbstractProcessingFilter filter = new MockAbstractProcessingFilter();
filter.setAuthenticationManager(new MockAuthenticationManager());
- filter.setDefaultTargetUrl("/default");
filter.setFilterProcessesUrl("/p");
filter.setAuthenticationFailureUrl("/fail");
filter.afterPropertiesSet();
@@ -200,24 +193,10 @@ public class AbstractProcessingFilterTests extends TestCase {
filter.setRememberMeServices(new TokenBasedRememberMeServices());
assertEquals(TokenBasedRememberMeServices.class, filter.getRememberMeServices().getClass());
assertTrue(filter.getAuthenticationManager() != null);
- assertEquals("/default", filter.getDefaultTargetUrl());
assertEquals("/p", filter.getFilterProcessesUrl());
assertEquals("/fail", filter.getAuthenticationFailureUrl());
}
- public void testDefaultUrlMuststartWithSlashOrHttpScheme() {
- AbstractProcessingFilter filter = new MockAbstractProcessingFilter();
-
- filter.setDefaultTargetUrl("/acceptableRelativeUrl");
- filter.setDefaultTargetUrl("http://some.site.org/index.html");
- filter.setDefaultTargetUrl("https://some.site.org/index.html");
-
- try {
- filter.setDefaultTargetUrl("missingSlash");
- fail("Shouldn't accept default target without leading slash");
- } catch (IllegalArgumentException expected) {}
- }
-
public void testIgnoresAnyServletPathOtherThanFilterProcessesUrl() throws Exception {
// Setup our HTTP request
MockHttpServletRequest request = createMockRequest();
@@ -252,8 +231,9 @@ public class AbstractProcessingFilterTests extends TestCase {
// Setup our test object, to grant access
MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true);
+
filter.setFilterProcessesUrl("/j_mock_post");
- filter.setDefaultTargetUrl("/logged_in.jsp");
+ filter.setSuccessHandler(successHandler);
filter.setAuthenticationFailureUrl("/failure.jsp");
filter.setAuthenticationManager(new MockAuthenticationManager(true));
filter.afterPropertiesSet();
@@ -270,7 +250,8 @@ public class AbstractProcessingFilterTests extends TestCase {
public void testStartupDetectsInvalidAuthenticationManager() throws Exception {
AbstractProcessingFilter filter = new MockAbstractProcessingFilter();
filter.setAuthenticationFailureUrl("/failed.jsp");
- filter.setDefaultTargetUrl("/");
+ successHandler.setDefaultTargetUrl("/");
+ filter.setSuccessHandler(successHandler);
filter.setFilterProcessesUrl("/j_spring_security_check");
try {
@@ -281,25 +262,11 @@ public class AbstractProcessingFilterTests extends TestCase {
}
}
- public void testStartupDetectsInvalidDefaultTargetUrl() throws Exception {
- AbstractProcessingFilter filter = new MockAbstractProcessingFilter();
- filter.setAuthenticationFailureUrl("/failed.jsp");
- filter.setAuthenticationManager(new MockAuthenticationManager());
- filter.setFilterProcessesUrl("/j_spring_security_check");
-
- try {
- filter.afterPropertiesSet();
- fail("Should have thrown IllegalArgumentException");
- } catch (IllegalArgumentException expected) {
- assertEquals("defaultTargetUrl must be specified", expected.getMessage());
- }
- }
-
public void testStartupDetectsInvalidFilterProcessesUrl() throws Exception {
AbstractProcessingFilter filter = new MockAbstractProcessingFilter();
filter.setAuthenticationFailureUrl("/failed.jsp");
filter.setAuthenticationManager(new MockAuthenticationManager());
- filter.setDefaultTargetUrl("/");
+ filter.setSuccessHandler(successHandler);
filter.setFilterProcessesUrl(null);
try {
@@ -324,7 +291,7 @@ public class AbstractProcessingFilterTests extends TestCase {
// Setup our test object, to grant access
MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true);
filter.setFilterProcessesUrl("/j_mock_post");
- filter.setDefaultTargetUrl("/logged_in.jsp");
+ filter.setSuccessHandler(successHandler);
// Test
executeFilterInContainerSimulator(config, filter, request, response, chain);
@@ -364,10 +331,11 @@ public class AbstractProcessingFilterTests extends TestCase {
// Setup our test object, to grant access
MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true);
filter.setFilterProcessesUrl("/j_mock_post");
- filter.setDefaultTargetUrl("/foobar");
- assertFalse(filter.isAlwaysUseDefaultTargetUrl()); // check default
- filter.setAlwaysUseDefaultTargetUrl(true);
- assertTrue(filter.isAlwaysUseDefaultTargetUrl()); // check changed
+ successHandler.setDefaultTargetUrl("/foobar");
+ assertFalse(successHandler.isAlwaysUseDefaultTargetUrl()); // check default
+ successHandler.setAlwaysUseDefaultTargetUrl(true);
+ assertTrue(successHandler.isAlwaysUseDefaultTargetUrl()); // check changed
+ filter.setSuccessHandler(successHandler);
// Test
executeFilterInContainerSimulator(config, filter, request, response, chain);
@@ -413,12 +381,10 @@ public class AbstractProcessingFilterTests extends TestCase {
MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true);
filter.setFilterProcessesUrl("/j_mock_post");
- filter.setDefaultTargetUrl("/foobar");
-
- // Configure target resolver default implementation not to use POST SavedRequest
- TargetUrlResolverImpl targetUrlResolver = new TargetUrlResolverImpl();
- targetUrlResolver.setJustUseSavedRequestOnGet(true);
- filter.setTargetUrlResolver(targetUrlResolver);
+ filter.setSuccessHandler(successHandler);
+ successHandler.setDefaultTargetUrl("/foobar");
+ // Configure not to use POST SavedRequest
+ successHandler.setJustUseSavedRequestOnGet(true);
// Test
executeFilterInContainerSimulator(config, filter, request, response, chain);
@@ -438,8 +404,9 @@ public class AbstractProcessingFilterTests extends TestCase {
// Setup our test object, to grant access
MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true);
- filter.setDefaultTargetUrl("https://monkeymachine.co.uk/");
- filter.setAlwaysUseDefaultTargetUrl(true);
+ successHandler.setDefaultTargetUrl("https://monkeymachine.co.uk/");
+ successHandler.setAlwaysUseDefaultTargetUrl(true);
+ filter.setSuccessHandler(successHandler);
executeFilterInContainerSimulator(config, filter, request, response, chain);
assertEquals("https://monkeymachine.co.uk/", response.getRedirectedUrl());
@@ -458,7 +425,8 @@ public class AbstractProcessingFilterTests extends TestCase {
// Setup our test object, to grant access
MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true);
filter.setInvalidateSessionOnSuccessfulAuthentication(true);
- filter.setDefaultTargetUrl("http://monkeymachine.co.uk/");
+ successHandler.setDefaultTargetUrl("http://monkeymachine.co.uk/");
+ filter.setSuccessHandler(successHandler);
executeFilterInContainerSimulator(config, filter, request, response, chain);
@@ -477,7 +445,8 @@ public class AbstractProcessingFilterTests extends TestCase {
MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true);
filter.setInvalidateSessionOnSuccessfulAuthentication(true);
filter.setMigrateInvalidatedSessionAttributes(false);
- filter.setDefaultTargetUrl("http://monkeymachine.co.uk/");
+ successHandler.setDefaultTargetUrl("http://monkeymachine.co.uk/");
+ filter.setSuccessHandler(successHandler);
executeFilterInContainerSimulator(config, filter, request, response, chain);
@@ -500,7 +469,8 @@ public class AbstractProcessingFilterTests extends TestCase {
MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(false);
filter.setAllowSessionCreation(false);
filter.setAuthenticationFailureUrl("/");
- filter.setDefaultTargetUrl("http://monkeymachine.co.uk/");
+ successHandler.setDefaultTargetUrl("http://monkeymachine.co.uk/");
+ filter.setSuccessHandler(successHandler);
executeFilterInContainerSimulator(config, filter, request, response, chain);
@@ -518,7 +488,8 @@ public class AbstractProcessingFilterTests extends TestCase {
MockHttpServletResponse response = new MockHttpServletResponse();
MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(false);
- filter.setDefaultTargetUrl("http://monkeymachine.co.uk/");
+ successHandler.setDefaultTargetUrl("http://monkeymachine.co.uk/");
+ filter.setSuccessHandler(successHandler);
executeFilterInContainerSimulator(config, filter, request, response, chain);
@@ -536,7 +507,8 @@ public class AbstractProcessingFilterTests extends TestCase {
MockHttpServletResponse response = new MockHttpServletResponse();
MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(false);
- filter.setDefaultTargetUrl("http://monkeymachine.co.uk/");
+ successHandler.setDefaultTargetUrl("http://monkeymachine.co.uk/");
+ filter.setSuccessHandler(successHandler);
filter.setAuthenticationFailureUrl("/error");
filter.setServerSideRedirect(true);
@@ -557,10 +529,9 @@ public class AbstractProcessingFilterTests extends TestCase {
MockHttpServletResponse response = new MockHttpServletResponse();
MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true);
- TargetUrlResolverImpl targetUrlResolver = new TargetUrlResolverImpl();
- targetUrlResolver.setTargetUrlParameter("targetUrl");
- filter.setTargetUrlResolver(targetUrlResolver);
- filter.setDefaultTargetUrl("http://monkeymachine.co.uk/");
+ filter.setSuccessHandler(successHandler);
+ successHandler.setDefaultTargetUrl("http://monkeymachine.co.uk/");
+ successHandler.setTargetUrlParameter("targetUrl");
filter.setAuthenticationFailureUrl("/error");
executeFilterInContainerSimulator(config, filter, request, response, chain);
diff --git a/core/src/test/java/org/springframework/security/ui/SavedRequestAwareAuthenticationSuccessHandlerTests.java b/core/src/test/java/org/springframework/security/ui/SavedRequestAwareAuthenticationSuccessHandlerTests.java
new file mode 100644
index 0000000000..b8cecb1e5d
--- /dev/null
+++ b/core/src/test/java/org/springframework/security/ui/SavedRequestAwareAuthenticationSuccessHandlerTests.java
@@ -0,0 +1,22 @@
+package org.springframework.security.ui;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class SavedRequestAwareAuthenticationSuccessHandlerTests {
+
+ @Test
+ public void defaultUrlMuststartWithSlashOrHttpScheme() {
+ SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler();
+
+ handler.setDefaultTargetUrl("/acceptableRelativeUrl");
+ handler.setDefaultTargetUrl("http://some.site.org/index.html");
+ handler.setDefaultTargetUrl("https://some.site.org/index.html");
+
+ try {
+ handler.setDefaultTargetUrl("missingSlash");
+ fail("Shouldn't accept default target without leading slash");
+ } catch (IllegalArgumentException expected) {}
+ }
+}
diff --git a/core/src/test/resources/org/springframework/security/util/filtertest-valid.xml b/core/src/test/resources/org/springframework/security/util/filtertest-valid.xml
index 3e74d90f27..0467f22b5e 100644
--- a/core/src/test/resources/org/springframework/security/util/filtertest-valid.xml
+++ b/core/src/test/resources/org/springframework/security/util/filtertest-valid.xml
@@ -28,11 +28,10 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc