소스 검색

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.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);

+ 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.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)) {

+ 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 &=
 	## 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 =

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

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

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

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

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