浏览代码

Add Forward after authentication attempt config support

Fixes gh-3728
Shazin Sadakath 9 年之前
父节点
当前提交
e33e21fe6b

+ 25 - 0
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.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);

+ 24 - 9
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.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.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.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 {
+		} else if(StringUtils.hasText(authenticationSuccessForwardUrl)) {
+			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 {
+		} else if(StringUtils.hasText(authenticationFailureForwardUrl)) {
+			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)) {

+ 6 - 0
config/src/main/resources/org/springframework/security/config/spring-security-4.1.rnc

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

+ 12 - 0
config/src/main/resources/org/springframework/security/config/spring-security-4.1.xsd

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

+ 51 - 0
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.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

+ 40 - 0
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.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"
+	}
 }
 }

+ 18 - 0
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**
 * **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