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.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;
* </ul>
*
* @author Rob Winch
* @author Shazin Sadakath
* @since 3.2
*/
public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>>
@ -206,6 +209,28 @@ public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>>
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
public void init(H http) throws Exception {
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.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)) {

View File

@ -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 =

View File

@ -1465,6 +1465,18 @@
</xs:documentation>
</xs:annotation>
</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: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.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

View File

@ -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 {
</table>
</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**
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]]
==== <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 <<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