Add Forward after authentication attempt config support

Fixes gh-3728
This commit is contained in:
Shazin Sadakath 2016-03-10 22:09:12 +05:30 committed by Rob Winch
parent dbf73c4692
commit e33e21fe6b
7 changed files with 176 additions and 9 deletions

View File

@ -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.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.AuthenticationEntryPoint; 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.RememberMeServices;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
@ -62,6 +64,7 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
* </ul> * </ul>
* *
* @author Rob Winch * @author Rob Winch
* @author Shazin Sadakath
* @since 3.2 * @since 3.2
*/ */
public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>>
@ -206,6 +209,28 @@ public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>>
return this; return this;
} }
/**
* Forward Authentication Failure Handler
*
* @param forwardUrl the target URL in case of failure
* @return he {@link FormLoginConfigurer} for additional customization
*/
public FormLoginConfigurer<H> 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<H> successForwardUrl(String forwardUrl) {
successHandler(new ForwardAuthenticationSuccessHandler(forwardUrl));
return this;
}
@Override @Override
public void init(H http) throws Exception { public void init(H http) throws Exception {
super.init(http); super.init(http);

View File

@ -22,9 +22,7 @@ import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.authentication.*;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.w3c.dom.Element; import org.w3c.dom.Element;
@ -34,6 +32,7 @@ import org.w3c.dom.Element;
* @author Ben Alex * @author Ben Alex
* @author Rob Winch * @author Rob Winch
* @author Kazuki Shimizu * @author Kazuki Shimizu
* @author Shazin Sadakath
*/ */
public class FormLoginBeanDefinitionParser { public class FormLoginBeanDefinitionParser {
protected final Log logger = LogFactory.getLog(getClass()); 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_SUCCESS_HANDLER_REF = "authentication-success-handler-ref";
private static final String ATT_FAILURE_HANDLER_REF = "authentication-failure-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 defaultLoginProcessingUrl;
private final String filterClassName; private final String filterClassName;
@ -95,6 +96,8 @@ public class FormLoginBeanDefinitionParser {
String usernameParameter = null; String usernameParameter = null;
String passwordParameter = null; String passwordParameter = null;
String authDetailsSourceRef = null; String authDetailsSourceRef = null;
String authenticationFailureForwardUrl = null;
String authenticationSuccessForwardUrl = null;
Object source = null; Object source = null;
@ -113,6 +116,10 @@ public class FormLoginBeanDefinitionParser {
failureHandlerRef = elt.getAttribute(ATT_FAILURE_HANDLER_REF); failureHandlerRef = elt.getAttribute(ATT_FAILURE_HANDLER_REF);
authDetailsSourceRef = elt authDetailsSourceRef = elt
.getAttribute(AuthenticationConfigBuilder.ATT_AUTH_DETAILS_SOURCE_REF); .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)) { if (!StringUtils.hasText(loginPage)) {
loginPage = null; loginPage = null;
@ -124,7 +131,7 @@ public class FormLoginBeanDefinitionParser {
filterBean = createFilterBean(loginUrl, defaultTargetUrl, alwaysUseDefault, filterBean = createFilterBean(loginUrl, defaultTargetUrl, alwaysUseDefault,
loginPage, authenticationFailureUrl, successHandlerRef, loginPage, authenticationFailureUrl, successHandlerRef,
failureHandlerRef, authDetailsSourceRef); failureHandlerRef, authDetailsSourceRef, authenticationFailureForwardUrl, authenticationSuccessForwardUrl);
if (StringUtils.hasText(usernameParameter)) { if (StringUtils.hasText(usernameParameter)) {
filterBean.getPropertyValues().addPropertyValue("usernameParameter", filterBean.getPropertyValues().addPropertyValue("usernameParameter",
@ -152,7 +159,7 @@ public class FormLoginBeanDefinitionParser {
private RootBeanDefinition createFilterBean(String loginUrl, String defaultTargetUrl, private RootBeanDefinition createFilterBean(String loginUrl, String defaultTargetUrl,
String alwaysUseDefault, String loginPage, String authenticationFailureUrl, String alwaysUseDefault, String loginPage, String authenticationFailureUrl,
String successHandlerRef, String failureHandlerRef, String successHandlerRef, String failureHandlerRef,
String authDetailsSourceRef) { String authDetailsSourceRef, String authenticationFailureForwardUrl, String authenticationSuccessForwardUrl) {
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder
.rootBeanDefinition(filterClassName); .rootBeanDefinition(filterClassName);
@ -176,8 +183,12 @@ public class FormLoginBeanDefinitionParser {
if (StringUtils.hasText(successHandlerRef)) { if (StringUtils.hasText(successHandlerRef)) {
filterBuilder.addPropertyReference("authenticationSuccessHandler", filterBuilder.addPropertyReference("authenticationSuccessHandler",
successHandlerRef); successHandlerRef);
} } else if(StringUtils.hasText(authenticationSuccessForwardUrl)) {
else { BeanDefinitionBuilder forwardSuccessHandler = BeanDefinitionBuilder
.rootBeanDefinition(ForwardAuthenticationSuccessHandler.class);
forwardSuccessHandler.addConstructorArgValue(authenticationSuccessForwardUrl);
filterBuilder.addPropertyValue("authenticationSuccessHandler", forwardSuccessHandler.getBeanDefinition());
} else {
BeanDefinitionBuilder successHandler = BeanDefinitionBuilder BeanDefinitionBuilder successHandler = BeanDefinitionBuilder
.rootBeanDefinition(SavedRequestAwareAuthenticationSuccessHandler.class); .rootBeanDefinition(SavedRequestAwareAuthenticationSuccessHandler.class);
if ("true".equals(alwaysUseDefault)) { if ("true".equals(alwaysUseDefault)) {
@ -205,8 +216,12 @@ public class FormLoginBeanDefinitionParser {
if (StringUtils.hasText(failureHandlerRef)) { if (StringUtils.hasText(failureHandlerRef)) {
filterBuilder.addPropertyReference("authenticationFailureHandler", filterBuilder.addPropertyReference("authenticationFailureHandler",
failureHandlerRef); failureHandlerRef);
} } else if(StringUtils.hasText(authenticationFailureForwardUrl)) {
else { BeanDefinitionBuilder forwardFailureHandler = BeanDefinitionBuilder
.rootBeanDefinition(ForwardAuthenticationFailureHandler.class);
forwardFailureHandler.addConstructorArgValue(authenticationFailureForwardUrl);
filterBuilder.addPropertyValue("authenticationFailureHandler", forwardFailureHandler.getBeanDefinition());
} else {
BeanDefinitionBuilder failureHandler = BeanDefinitionBuilder BeanDefinitionBuilder failureHandler = BeanDefinitionBuilder
.rootBeanDefinition(SimpleUrlAuthenticationFailureHandler.class); .rootBeanDefinition(SimpleUrlAuthenticationFailureHandler.class);
if (!StringUtils.hasText(authenticationFailureUrl)) { if (!StringUtils.hasText(authenticationFailureUrl)) {

View File

@ -438,6 +438,12 @@ form-login.attlist &=
form-login.attlist &= form-login.attlist &=
## Reference to an AuthenticationDetailsSource which will be used by the authentication filter ## Reference to an AuthenticationDetailsSource which will be used by the authentication filter
attribute authentication-details-source-ref {xsd:token}? 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 = openid-login =

View File

@ -1465,6 +1465,18 @@
</xs:documentation> </xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="authentication-failure-forward-url" type="xs:token">
<xs:annotation>
<xs:documentation>The URL for the ForwardAuthenticationFailureHandler
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="authentication-success-forward-url" type="xs:token">
<xs:annotation>
<xs:documentation>The URL for the ForwardAuthenticationSuccessHandler
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:element name="attribute-exchange"> <xs:element name="attribute-exchange">

View File

@ -17,6 +17,7 @@ package org.springframework.security.config.annotation.web.configurers
import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpMethod
import org.springframework.mock.web.MockFilterChain import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse 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.AuthenticationEntryPoint
import org.springframework.security.web.FilterChainProxy import org.springframework.security.web.FilterChainProxy
import org.springframework.security.web.PortMapper 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.ExceptionTranslationFilter
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter import org.springframework.security.web.authentication.AnonymousAuthenticationFilter
@ -281,6 +283,55 @@ class FormLoginConfigurerTests extends BaseSpringSpec {
findFilter(UsernamePasswordAuthenticationFilter).usernameParameter == "custom-username" 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 @EnableWebSecurity
static class DuplicateInvocationsDoesNotOverrideConfig extends BaseWebConfig { static class DuplicateInvocationsDoesNotOverrideConfig extends BaseWebConfig {
static AuthenticationFailureHandler FAILURE_HANDLER static AuthenticationFailureHandler FAILURE_HANDLER

View File

@ -3,6 +3,7 @@ package org.springframework.security.config.http
import org.springframework.mock.web.MockFilterChain import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.web.WebAttributes
/** /**
* *
@ -110,4 +111,43 @@ class FormLoginBeanDefinitionParserTests extends AbstractHttpConfigTests {
</table> </table>
</form></body></html>""" </form></body></html>"""
} }
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"
}
} }

View File

@ -7626,6 +7626,14 @@ The name of the request parameter which contains the password. Defaults to "pass
* **username-parameter** * **username-parameter**
The name of the request parameter which contains the username. Defaults to "username". 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]] [[nsa-http-basic]]
==== <http-basic> ==== <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. 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]] [[nsa-openid-login-authentication-success-handler-ref]]
* **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 <<nsa-openid-login-default-target-url,default-target-url>> (or <<nsa-openid-login-always-use-default-target, always-use-default-target>>) as the implementation should always deal with navigation to the subsequent destination Reference to an AuthenticationSuccessHandler bean which should be used to handle a successful authentication request. Should not be used in combination with <<nsa-openid-login-default-target-url,default-target-url>> (or <<nsa-openid-login-always-use-default-target, always-use-default-target>>) as the implementation should always deal with navigation to the subsequent destination