From e33e21fe6be9fac0871242b5fda2235f030afe1b Mon Sep 17 00:00:00 2001 From: Shazin Sadakath Date: Thu, 10 Mar 2016 22:09:12 +0530 Subject: [PATCH] Add Forward after authentication attempt config support Fixes gh-3728 --- .../web/configurers/FormLoginConfigurer.java | 25 +++++++++ .../http/FormLoginBeanDefinitionParser.java | 33 ++++++++---- .../security/config/spring-security-4.1.rnc | 6 +++ .../security/config/spring-security-4.1.xsd | 12 +++++ .../FormLoginConfigurerTests.groovy | 51 +++++++++++++++++++ .../FormLoginBeanDefinitionParserTests.groovy | 40 +++++++++++++++ docs/manual/src/docs/asciidoc/index.adoc | 18 +++++++ 7 files changed, 176 insertions(+), 9 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurer.java index 1fe93da3b1..5a76f9901e 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurer.java @@ -19,6 +19,8 @@ import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.authentication.ForwardAuthenticationFailureHandler; +import org.springframework.security.web.authentication.ForwardAuthenticationSuccessHandler; import org.springframework.security.web.authentication.RememberMeServices; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; @@ -62,6 +64,7 @@ import org.springframework.security.web.util.matcher.RequestMatcher; * * * @author Rob Winch + * @author Shazin Sadakath * @since 3.2 */ public final class FormLoginConfigurer> @@ -206,6 +209,28 @@ public final class FormLoginConfigurer> return this; } + /** + * Forward Authentication Failure Handler + * + * @param forwardUrl the target URL in case of failure + * @return he {@link FormLoginConfigurer} for additional customization + */ + public FormLoginConfigurer failureForwardUrl(String forwardUrl) { + failureHandler(new ForwardAuthenticationFailureHandler(forwardUrl)); + return this; + } + + /** + * Forward Authentication Success Handler + * + * @param forwardUrl the target URL in case of success + * @return he {@link FormLoginConfigurer} for additional customization + */ + public FormLoginConfigurer successForwardUrl(String forwardUrl) { + successHandler(new ForwardAuthenticationSuccessHandler(forwardUrl)); + return this; + } + @Override public void init(H http) throws Exception { super.init(http); diff --git a/config/src/main/java/org/springframework/security/config/http/FormLoginBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/FormLoginBeanDefinitionParser.java index b79237ece4..b20b434f20 100644 --- a/config/src/main/java/org/springframework/security/config/http/FormLoginBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/FormLoginBeanDefinitionParser.java @@ -22,9 +22,7 @@ import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; -import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.security.web.authentication.*; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; import org.springframework.util.StringUtils; import org.w3c.dom.Element; @@ -34,6 +32,7 @@ import org.w3c.dom.Element; * @author Ben Alex * @author Rob Winch * @author Kazuki Shimizu + * @author Shazin Sadakath */ public class FormLoginBeanDefinitionParser { protected final Log logger = LogFactory.getLog(getClass()); @@ -55,6 +54,8 @@ public class FormLoginBeanDefinitionParser { private static final String ATT_SUCCESS_HANDLER_REF = "authentication-success-handler-ref"; private static final String ATT_FAILURE_HANDLER_REF = "authentication-failure-handler-ref"; + private static final String ATT_FORM_LOGIN_AUTHENTICATION_FAILURE_FORWARD_URL = "authentication-failure-forward-url"; + private static final String ATT_FORM_LOGIN_AUTHENTICATION_SUCCESS_FORWARD_URL = "authentication-success-forward-url"; private final String defaultLoginProcessingUrl; private final String filterClassName; @@ -95,6 +96,8 @@ public class FormLoginBeanDefinitionParser { String usernameParameter = null; String passwordParameter = null; String authDetailsSourceRef = null; + String authenticationFailureForwardUrl = null; + String authenticationSuccessForwardUrl = null; Object source = null; @@ -113,6 +116,10 @@ public class FormLoginBeanDefinitionParser { failureHandlerRef = elt.getAttribute(ATT_FAILURE_HANDLER_REF); authDetailsSourceRef = elt .getAttribute(AuthenticationConfigBuilder.ATT_AUTH_DETAILS_SOURCE_REF); + authenticationFailureForwardUrl = elt.getAttribute(ATT_FORM_LOGIN_AUTHENTICATION_FAILURE_FORWARD_URL); + WebConfigUtils.validateHttpRedirect(authenticationFailureForwardUrl, pc, source); + authenticationSuccessForwardUrl = elt.getAttribute(ATT_FORM_LOGIN_AUTHENTICATION_SUCCESS_FORWARD_URL); + WebConfigUtils.validateHttpRedirect(authenticationSuccessForwardUrl, pc, source); if (!StringUtils.hasText(loginPage)) { loginPage = null; @@ -124,7 +131,7 @@ public class FormLoginBeanDefinitionParser { filterBean = createFilterBean(loginUrl, defaultTargetUrl, alwaysUseDefault, loginPage, authenticationFailureUrl, successHandlerRef, - failureHandlerRef, authDetailsSourceRef); + failureHandlerRef, authDetailsSourceRef, authenticationFailureForwardUrl, authenticationSuccessForwardUrl); if (StringUtils.hasText(usernameParameter)) { filterBean.getPropertyValues().addPropertyValue("usernameParameter", @@ -152,7 +159,7 @@ public class FormLoginBeanDefinitionParser { private RootBeanDefinition createFilterBean(String loginUrl, String defaultTargetUrl, String alwaysUseDefault, String loginPage, String authenticationFailureUrl, String successHandlerRef, String failureHandlerRef, - String authDetailsSourceRef) { + String authDetailsSourceRef, String authenticationFailureForwardUrl, String authenticationSuccessForwardUrl) { BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder .rootBeanDefinition(filterClassName); @@ -176,8 +183,12 @@ public class FormLoginBeanDefinitionParser { if (StringUtils.hasText(successHandlerRef)) { filterBuilder.addPropertyReference("authenticationSuccessHandler", successHandlerRef); - } - else { + } else if(StringUtils.hasText(authenticationSuccessForwardUrl)) { + BeanDefinitionBuilder forwardSuccessHandler = BeanDefinitionBuilder + .rootBeanDefinition(ForwardAuthenticationSuccessHandler.class); + forwardSuccessHandler.addConstructorArgValue(authenticationSuccessForwardUrl); + filterBuilder.addPropertyValue("authenticationSuccessHandler", forwardSuccessHandler.getBeanDefinition()); + } else { BeanDefinitionBuilder successHandler = BeanDefinitionBuilder .rootBeanDefinition(SavedRequestAwareAuthenticationSuccessHandler.class); if ("true".equals(alwaysUseDefault)) { @@ -205,8 +216,12 @@ public class FormLoginBeanDefinitionParser { if (StringUtils.hasText(failureHandlerRef)) { filterBuilder.addPropertyReference("authenticationFailureHandler", failureHandlerRef); - } - else { + } else if(StringUtils.hasText(authenticationFailureForwardUrl)) { + BeanDefinitionBuilder forwardFailureHandler = BeanDefinitionBuilder + .rootBeanDefinition(ForwardAuthenticationFailureHandler.class); + forwardFailureHandler.addConstructorArgValue(authenticationFailureForwardUrl); + filterBuilder.addPropertyValue("authenticationFailureHandler", forwardFailureHandler.getBeanDefinition()); + } else { BeanDefinitionBuilder failureHandler = BeanDefinitionBuilder .rootBeanDefinition(SimpleUrlAuthenticationFailureHandler.class); if (!StringUtils.hasText(authenticationFailureUrl)) { diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-4.1.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-4.1.rnc index 3d154b1bfc..88fb85d780 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-4.1.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-4.1.rnc @@ -438,6 +438,12 @@ form-login.attlist &= form-login.attlist &= ## Reference to an AuthenticationDetailsSource which will be used by the authentication filter attribute authentication-details-source-ref {xsd:token}? +form-login.attlist &= + ## The URL for the ForwardAuthenticationFailureHandler + attribute authentication-failure-forward-url {xsd:token}? +form-login.attlist &= + ## The URL for the ForwardAuthenticationSuccessHandler + attribute authentication-success-forward-url {xsd:token}? openid-login = diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-4.1.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-4.1.xsd index c925ca11c1..90ef08ab80 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-4.1.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-4.1.xsd @@ -1465,6 +1465,18 @@ + + + The URL for the ForwardAuthenticationFailureHandler + + + + + + The URL for the ForwardAuthenticationSuccessHandler + + + diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy index ee079aea83..1f9eeea0d3 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy @@ -17,6 +17,7 @@ package org.springframework.security.config.annotation.web.configurers import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.context.annotation.Configuration +import org.springframework.http.HttpMethod import org.springframework.mock.web.MockFilterChain import org.springframework.mock.web.MockHttpServletRequest import org.springframework.mock.web.MockHttpServletResponse @@ -31,6 +32,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe import org.springframework.security.web.AuthenticationEntryPoint import org.springframework.security.web.FilterChainProxy import org.springframework.security.web.PortMapper +import org.springframework.security.web.WebAttributes import org.springframework.security.web.access.ExceptionTranslationFilter import org.springframework.security.web.access.intercept.FilterSecurityInterceptor import org.springframework.security.web.authentication.AnonymousAuthenticationFilter @@ -281,6 +283,55 @@ class FormLoginConfigurerTests extends BaseSpringSpec { findFilter(UsernamePasswordAuthenticationFilter).usernameParameter == "custom-username" } + def "FormLogin permitAll uses Failure Forward Url when ForwardAuthenticationFailureHandler set"() { + setup: + loadConfig(FormLoginUserForwardAuthenticationSuccessAndFailureConfig) + FilterChainProxy springSecurityFilterChain = context.getBean(FilterChainProxy) + when: "access configured explicit ForwardFailureFailureHandler" + MockHttpServletRequest request = new MockHttpServletRequest(servletPath:"/login",method:"POST") + request.setParameter("username", "user"); + request.setParameter("password", "invalidpassword"); + MockHttpServletResponse response = new MockHttpServletResponse() + springSecurityFilterChain.doFilter(request,response,new MockFilterChain()) + then: "access is granted to the failure handler" + response.status == 200 + response.forwardedUrl == "/failure_forward_url" + request.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION) != null + } + + def "FormLogin permitAll uses Success Forward Url when ForwardAuthenticationSuccessHandler set"() { + setup: + loadConfig(FormLoginUserForwardAuthenticationSuccessAndFailureConfig) + FilterChainProxy springSecurityFilterChain = context.getBean(FilterChainProxy) + when: "access configured explicit ForwardSuccessAuthenticationHandler" + MockHttpServletRequest request = new MockHttpServletRequest(servletPath:"/login",method:"POST") + request.setParameter("username", "user"); + request.setParameter("password", "password"); + MockHttpServletResponse response = new MockHttpServletResponse() + springSecurityFilterChain.doFilter(request,response,new MockFilterChain()) + then: "access is granted to the success handler" + response.status == 200 + response.forwardedUrl == "/success_forward_url" + } + + @EnableWebSecurity + static class FormLoginUserForwardAuthenticationSuccessAndFailureConfig extends BaseWebConfig { + + @Override + protected void configure(HttpSecurity http) { + http.csrf() + .disable() + .authorizeRequests() + .anyRequest().authenticated() + .and() + .formLogin() + .failureForwardUrl("/failure_forward_url") + .successForwardUrl("/success_forward_url") + .permitAll() + + } + } + @EnableWebSecurity static class DuplicateInvocationsDoesNotOverrideConfig extends BaseWebConfig { static AuthenticationFailureHandler FAILURE_HANDLER diff --git a/config/src/test/groovy/org/springframework/security/config/http/FormLoginBeanDefinitionParserTests.groovy b/config/src/test/groovy/org/springframework/security/config/http/FormLoginBeanDefinitionParserTests.groovy index 801e2820ba..4df5946b4d 100644 --- a/config/src/test/groovy/org/springframework/security/config/http/FormLoginBeanDefinitionParserTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/http/FormLoginBeanDefinitionParserTests.groovy @@ -3,6 +3,7 @@ package org.springframework.security.config.http import org.springframework.mock.web.MockFilterChain import org.springframework.mock.web.MockHttpServletRequest import org.springframework.mock.web.MockHttpServletResponse +import org.springframework.security.web.WebAttributes /** * @@ -110,4 +111,43 @@ class FormLoginBeanDefinitionParserTests extends AbstractHttpConfigTests { """ } + + def 'form-login forward authentication failure handler'() { + setup: + MockHttpServletRequest request = new MockHttpServletRequest(method:'POST',servletPath:'/login') + request.setParameter("username", "bob") + request.setParameter("password", "invalidpassword") + MockHttpServletResponse response = new MockHttpServletResponse() + MockFilterChain chain = new MockFilterChain() + httpAutoConfig { + 'form-login'('authentication-failure-forward-url':'/failure_forward_url') + csrf(disabled:true) + } + createAppContext() + when: + springSecurityFilterChain.doFilter(request,response,chain) + then: + response.getStatus() == 200 + response.forwardedUrl == "/failure_forward_url" + request.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION) != null; + } + + def 'form-login forward authentication success handler'() { + setup: + MockHttpServletRequest request = new MockHttpServletRequest(method:'POST',servletPath:'/login') + request.setParameter("username", "bob") + request.setParameter("password", "bobspassword") + MockHttpServletResponse response = new MockHttpServletResponse() + MockFilterChain chain = new MockFilterChain() + httpAutoConfig { + 'form-login'('authentication-success-forward-url':'/success_forward_url') + csrf(disabled:true) + } + createAppContext() + when: + springSecurityFilterChain.doFilter(request,response,chain) + then: + response.getStatus() == 200 + response.forwardedUrl == "/success_forward_url" + } } diff --git a/docs/manual/src/docs/asciidoc/index.adoc b/docs/manual/src/docs/asciidoc/index.adoc index 7332b0cc3b..6c2fea6b3d 100644 --- a/docs/manual/src/docs/asciidoc/index.adoc +++ b/docs/manual/src/docs/asciidoc/index.adoc @@ -7626,6 +7626,14 @@ The name of the request parameter which contains the password. Defaults to "pass * **username-parameter** The name of the request parameter which contains the username. Defaults to "username". +[[nsa-form-login-authentication-success-forward-url]] +* **authentication-success-forward-url** +Maps a `ForwardAuthenticationSuccessHandler` to `authenticationSuccessHandler` property of `UsernamePasswordAuthenticationFilter`. + + +[[nsa-form-login-authentication-failure-forward-url]] +* **authentication-failure-forward-url** +Maps a `ForwardAuthenticationFailureHandler` to `authenticationFailureHandler` property of `UsernamePasswordAuthenticationFilter`. [[nsa-http-basic]] ==== @@ -7826,6 +7834,16 @@ Reference to an AuthenticationFailureHandler bean which should be used to handle The URL for the login failure page. If no login failure URL is specified, Spring Security will automatically create a failure login URL at /login?login_error and a corresponding filter to render that login failure URL when requested. +[[nsa-openid-login-authentication-success-forward-url]] +* **authentication-success-forward-url** +Maps a `ForwardAuthenticationSuccessHandler` to `authenticationSuccessHandler` property of `UsernamePasswordAuthenticationFilter`. + + +[[nsa-openid-login-authentication-failure-forward-url]] +* **authentication-failure-forward-url** +Maps a `ForwardAuthenticationFailureHandler` to `authenticationFailureHandler` property of `UsernamePasswordAuthenticationFilter`. + + [[nsa-openid-login-authentication-success-handler-ref]] * **authentication-success-handler-ref** Reference to an AuthenticationSuccessHandler bean which should be used to handle a successful authentication request. Should not be used in combination with <> (or <>) as the implementation should always deal with navigation to the subsequent destination