瀏覽代碼

Redirect to Appropriate Entry Point Based on Missing Authorities

Issue gh-17934
Josh Cummings 2 月之前
父節點
當前提交
e66c498d80

+ 159 - 74
config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java

@@ -19,9 +19,8 @@ package org.springframework.security.config.annotation.web.configurers;
 import java.io.IOException;
 import java.util.Collection;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
-import java.util.function.Function;
-import java.util.stream.Collectors;
 
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
@@ -35,29 +34,22 @@ import org.springframework.security.authorization.AuthorizationDeniedException;
 import org.springframework.security.config.Customizer;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.core.context.SecurityContextHolderStrategy;
-import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
 import org.springframework.security.web.AuthenticationEntryPoint;
-import org.springframework.security.web.FormPostRedirectStrategy;
-import org.springframework.security.web.RedirectStrategy;
 import org.springframework.security.web.access.AccessDeniedHandler;
 import org.springframework.security.web.access.AccessDeniedHandlerImpl;
 import org.springframework.security.web.access.ExceptionTranslationFilter;
 import org.springframework.security.web.access.RequestMatcherDelegatingAccessDeniedHandler;
 import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint;
 import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
-import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
-import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenFilter;
-import org.springframework.security.web.csrf.CsrfToken;
 import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
+import org.springframework.security.web.savedrequest.NullRequestCache;
 import org.springframework.security.web.savedrequest.RequestCache;
+import org.springframework.security.web.util.ThrowableAnalyzer;
+import org.springframework.security.web.util.matcher.AnyRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
-import org.springframework.web.util.UriComponentsBuilder;
 
 /**
  * Adds exception handling for Spring Security related exceptions to an application. All
@@ -102,6 +94,8 @@ public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>>
 
 	private LinkedHashMap<RequestMatcher, AccessDeniedHandler> defaultDeniedHandlerMappings = new LinkedHashMap<>();
 
+	private Map<String, LinkedHashMap<RequestMatcher, AuthenticationEntryPoint>> entryPoints = new LinkedHashMap<>();
+
 	/**
 	 * Creates a new instance
 	 * @see HttpSecurity#exceptionHandling(Customizer)
@@ -195,6 +189,26 @@ public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>>
 		return this;
 	}
 
+	public ExceptionHandlingConfigurer<H> defaultAuthenticationEntryPointFor(AuthenticationEntryPoint entryPoint,
+			RequestMatcher preferredMatcher, String authority) {
+		this.defaultEntryPointMappings.put(preferredMatcher, entryPoint);
+		LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> byMatcher = this.entryPoints.get(authority);
+		if (byMatcher == null) {
+			byMatcher = new LinkedHashMap<>();
+		}
+		byMatcher.put(preferredMatcher, entryPoint);
+		this.entryPoints.put(authority, byMatcher);
+		return this;
+	}
+
+	public ExceptionHandlingConfigurer<H> defaultAuthenticationEntryPointFor(AuthenticationEntryPoint entryPoint,
+			String authority) {
+		LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> byMatcher = new LinkedHashMap<>();
+		byMatcher.put(AnyRequestMatcher.INSTANCE, entryPoint);
+		this.entryPoints.put(authority, byMatcher);
+		return this;
+	}
+
 	/**
 	 * Gets any explicitly configured {@link AuthenticationEntryPoint}
 	 * @return
@@ -254,21 +268,60 @@ public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>>
 	}
 
 	private AccessDeniedHandler createDefaultDeniedHandler(H http) {
+		AccessDeniedHandler defaults = createDefaultAccessDeniedHandler(http);
+		if (this.entryPoints.isEmpty()) {
+			return defaults;
+		}
+		Map<String, AccessDeniedHandler> deniedHandlers = new LinkedHashMap<>();
+		for (Map.Entry<String, LinkedHashMap<RequestMatcher, AuthenticationEntryPoint>> entry : this.entryPoints
+			.entrySet()) {
+			AuthenticationEntryPoint entryPoint = entryPointFrom(entry.getValue());
+			AuthenticationEntryPointAccessDeniedHandlerAdapter deniedHandler = new AuthenticationEntryPointAccessDeniedHandlerAdapter(
+					entryPoint);
+			RequestCache requestCache = http.getSharedObject(RequestCache.class);
+			if (requestCache != null) {
+				deniedHandler.setRequestCache(requestCache);
+			}
+			deniedHandlers.put(entry.getKey(), deniedHandler);
+		}
+		return new AuthenticationFactorDelegatingAccessDeniedHandler(deniedHandlers, defaults);
+	}
+
+	private AccessDeniedHandler createDefaultAccessDeniedHandler(H http) {
 		if (this.defaultDeniedHandlerMappings.isEmpty()) {
-			return new AuthenticationFactorDelegatingAccessDeniedHandler();
+			return new AccessDeniedHandlerImpl();
 		}
 		if (this.defaultDeniedHandlerMappings.size() == 1) {
 			return this.defaultDeniedHandlerMappings.values().iterator().next();
 		}
 		return new RequestMatcherDelegatingAccessDeniedHandler(this.defaultDeniedHandlerMappings,
-				new AuthenticationFactorDelegatingAccessDeniedHandler());
+				new AccessDeniedHandlerImpl());
 	}
 
 	private AuthenticationEntryPoint createDefaultEntryPoint(H http) {
-		if (this.defaultEntryPoint == null) {
+		AuthenticationEntryPoint defaults = entryPointFrom(this.defaultEntryPointMappings);
+		if (this.entryPoints.isEmpty()) {
+			return defaults;
+		}
+		Map<String, AuthenticationEntryPoint> entryPoints = new LinkedHashMap<>();
+		for (Map.Entry<String, LinkedHashMap<RequestMatcher, AuthenticationEntryPoint>> entry : this.entryPoints
+			.entrySet()) {
+			entryPoints.put(entry.getKey(), entryPointFrom(entry.getValue()));
+		}
+		return new AuthenticationFactorDelegatingAuthenticationEntryPoint(entryPoints, defaults);
+	}
+
+	private AuthenticationEntryPoint entryPointFrom(
+			LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> entryPoints) {
+		if (entryPoints.isEmpty()) {
 			return new Http403ForbiddenEntryPoint();
 		}
-		return this.defaultEntryPoint.build();
+		if (entryPoints.size() == 1) {
+			return entryPoints.values().iterator().next();
+		}
+		DelegatingAuthenticationEntryPoint entryPoint = new DelegatingAuthenticationEntryPoint(entryPoints);
+		entryPoint.setDefaultEntryPoint(entryPoints.values().iterator().next());
+		return entryPoint;
 	}
 
 	/**
@@ -287,94 +340,126 @@ public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>>
 		return new HttpSessionRequestCache();
 	}
 
-	private static final class AuthenticationFactorDelegatingAccessDeniedHandler implements AccessDeniedHandler {
+	private static final class AuthenticationFactorDelegatingAuthenticationEntryPoint
+			implements AuthenticationEntryPoint {
+
+		private final ThrowableAnalyzer throwableAnalyzer = new ThrowableAnalyzer();
 
-		private final Map<String, AuthenticationEntryPoint> entryPoints = Map.of("FACTOR_PASSWORD",
-				new LoginUrlAuthenticationEntryPoint("/login"), "FACTOR_AUTHORIZATION_CODE",
-				new LoginUrlAuthenticationEntryPoint("/login"), "FACTOR_SAML_RESPONSE",
-				new LoginUrlAuthenticationEntryPoint("/login"), "FACTOR_WEBAUTHN",
-				new LoginUrlAuthenticationEntryPoint("/login"), "FACTOR_BEARER",
-				new BearerTokenAuthenticationEntryPoint(), "FACTOR_OTT",
-				new PostAuthenticationEntryPoint(GenerateOneTimeTokenFilter.DEFAULT_GENERATE_URL + "?username={u}",
-						Map.of("u", Authentication::getName)));
+		private final Map<String, AuthenticationEntryPoint> entryPoints;
 
-		private final AccessDeniedHandler defaults = new AccessDeniedHandlerImpl();
+		private final AuthenticationEntryPoint defaults;
+
+		private AuthenticationFactorDelegatingAuthenticationEntryPoint(
+				Map<String, AuthenticationEntryPoint> entryPoints, AuthenticationEntryPoint defaults) {
+			this.entryPoints = new LinkedHashMap<>(entryPoints);
+			this.defaults = defaults;
+		}
 
 		@Override
-		public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex)
+		public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex)
 				throws IOException, ServletException {
-			Collection<String> needed = authorizationRequest(ex);
-			if (needed == null) {
-				this.defaults.handle(request, response, ex);
-				return;
+			Collection<GrantedAuthority> authorization = authorizationRequest(ex);
+			entryPoint(authorization).commence(request, response, ex);
+		}
+
+		private AuthenticationEntryPoint entryPoint(Collection<GrantedAuthority> authorities) {
+			if (authorities == null) {
+				return this.defaults;
 			}
-			for (String authority : needed) {
-				AuthenticationEntryPoint entryPoint = this.entryPoints.get(authority);
+			for (GrantedAuthority needed : authorities) {
+				AuthenticationEntryPoint entryPoint = this.entryPoints.get(needed.getAuthority());
 				if (entryPoint != null) {
-					AuthenticationException insufficient = new InsufficientAuthenticationException(ex.getMessage(), ex);
-					entryPoint.commence(request, response, insufficient);
-					return;
+					return entryPoint;
 				}
 			}
-			this.defaults.handle(request, response, ex);
+			return this.defaults;
 		}
 
-		private Collection<String> authorizationRequest(AccessDeniedException access) {
-			if (!(access instanceof AuthorizationDeniedException denied)) {
-				return null;
+		private Collection<GrantedAuthority> authorizationRequest(Exception ex) {
+			Throwable[] chain = this.throwableAnalyzer.determineCauseChain(ex);
+			AuthorizationDeniedException denied = (AuthorizationDeniedException) this.throwableAnalyzer
+				.getFirstThrowableOfType(AuthorizationDeniedException.class, chain);
+			if (denied == null) {
+				return List.of();
 			}
-			if (!(denied.getAuthorizationResult() instanceof AuthorityAuthorizationDecision decision)) {
-				return null;
+			if (!(denied.getAuthorizationResult() instanceof AuthorityAuthorizationDecision authorization)) {
+				return List.of();
 			}
-			return decision.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList();
+			return authorization.getAuthorities();
 		}
 
 	}
 
-	private static final class PostAuthenticationEntryPoint implements AuthenticationEntryPoint {
+	private static final class AuthenticationEntryPointAccessDeniedHandlerAdapter implements AccessDeniedHandler {
+
+		private final AuthenticationEntryPoint entryPoint;
+
+		private RequestCache requestCache = new NullRequestCache();
+
+		private AuthenticationEntryPointAccessDeniedHandlerAdapter(AuthenticationEntryPoint entryPoint) {
+			this.entryPoint = entryPoint;
+		}
+
+		void setRequestCache(RequestCache requestCache) {
+			Assert.notNull(requestCache, "requestCache cannot be null");
+			this.requestCache = requestCache;
+		}
+
+		@Override
+		public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException denied)
+				throws IOException, ServletException {
+			AuthenticationException ex = new InsufficientAuthenticationException("access denied", denied);
+			this.requestCache.saveRequest(request, response);
+			this.entryPoint.commence(request, response, ex);
+		}
 
-		private final String entryPointUri;
+	}
+
+	private static final class AuthenticationFactorDelegatingAccessDeniedHandler implements AccessDeniedHandler {
 
-		private final Map<String, Function<Authentication, String>> params;
+		private final ThrowableAnalyzer throwableAnalyzer = new ThrowableAnalyzer();
 
-		private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
-			.getContextHolderStrategy();
+		private final Map<String, AccessDeniedHandler> deniedHandlers;
 
-		private RedirectStrategy redirectStrategy = new FormPostRedirectStrategy();
+		private final AccessDeniedHandler defaults;
 
-		private PostAuthenticationEntryPoint(String entryPointUri,
-				Map<String, Function<Authentication, String>> params) {
-			this.entryPointUri = entryPointUri;
-			this.params = params;
+		private AuthenticationFactorDelegatingAccessDeniedHandler(Map<String, AccessDeniedHandler> deniedHandlers,
+				AccessDeniedHandler defaults) {
+			this.deniedHandlers = new LinkedHashMap<>(deniedHandlers);
+			this.defaults = defaults;
 		}
 
 		@Override
-		public void commence(HttpServletRequest request, HttpServletResponse response,
-				AuthenticationException authException) throws IOException, ServletException {
-			Authentication authentication = getAuthentication(authException);
-			Assert.notNull(authentication, "could not find authentication in order to perform post");
-			Map<String, String> params = this.params.entrySet()
-				.stream()
-				.collect(Collectors.toMap(Map.Entry::getKey, (entry) -> entry.getValue().apply(authentication)));
-			UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(this.entryPointUri);
-			CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
-			if (csrf != null) {
-				builder.queryParam(csrf.getParameterName(), csrf.getToken());
+		public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex)
+				throws IOException, ServletException {
+			Collection<GrantedAuthority> authorization = authorizationRequest(ex);
+			deniedHandler(authorization).handle(request, response, ex);
+		}
+
+		private AccessDeniedHandler deniedHandler(Collection<GrantedAuthority> authorities) {
+			if (authorities == null) {
+				return this.defaults;
+			}
+			for (GrantedAuthority needed : authorities) {
+				AccessDeniedHandler deniedHandler = this.deniedHandlers.get(needed.getAuthority());
+				if (deniedHandler != null) {
+					return deniedHandler;
+				}
 			}
-			String entryPointUrl = builder.build(false).expand(params).toUriString();
-			this.redirectStrategy.sendRedirect(request, response, entryPointUrl);
+			return this.defaults;
 		}
 
-		private Authentication getAuthentication(AuthenticationException authException) {
-			Authentication authentication = authException.getAuthenticationRequest();
-			if (authentication != null && authentication.isAuthenticated()) {
-				return authentication;
+		private Collection<GrantedAuthority> authorizationRequest(Exception ex) {
+			Throwable[] chain = this.throwableAnalyzer.determineCauseChain(ex);
+			AuthorizationDeniedException denied = (AuthorizationDeniedException) this.throwableAnalyzer
+				.getFirstThrowableOfType(AuthorizationDeniedException.class, chain);
+			if (denied == null) {
+				return List.of();
 			}
-			authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
-			if (authentication != null && authentication.isAuthenticated()) {
-				return authentication;
+			if (!(denied.getAuthorizationResult() instanceof AuthorityAuthorizationDecision authorization)) {
+				return List.of();
 			}
-			return null;
+			return authorization.getAuthorities();
 		}
 
 	}

+ 4 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurer.java

@@ -231,6 +231,10 @@ public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
 	public void init(H http) throws Exception {
 		super.init(http);
 		initDefaultLoginFilter(http);
+		ExceptionHandlingConfigurer<H> exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class);
+		if (exceptions != null) {
+			exceptions.defaultAuthenticationEntryPointFor(getAuthenticationEntryPoint(), "FACTOR_PASSWORD");
+		}
 	}
 
 	@Override

+ 10 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java

@@ -28,6 +28,7 @@ import org.springframework.security.authentication.ProviderManager;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.web.access.intercept.AuthorizationFilter;
+import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
 import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
 import org.springframework.security.web.authentication.ui.DefaultResourcesFilter;
 import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
@@ -150,6 +151,15 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
 		return this;
 	}
 
+	@Override
+	public void init(H http) throws Exception {
+		ExceptionHandlingConfigurer<H> exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class);
+		if (exceptions != null) {
+			exceptions.defaultAuthenticationEntryPointFor(new LoginUrlAuthenticationEntryPoint("/login"),
+					"FACTOR_WEBAUTHN");
+		}
+	}
+
 	@Override
 	public void configure(H http) throws Exception {
 		UserDetailsService userDetailsService = getSharedOrBean(http, UserDetailsService.class)

+ 2 - 1
config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java

@@ -184,7 +184,8 @@ public final class X509Configurer<H extends HttpSecurityBuilder<H>>
 			.setSharedObject(AuthenticationEntryPoint.class, new Http403ForbiddenEntryPoint());
 		ExceptionHandlingConfigurer<H> exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class);
 		if (exceptions != null) {
-			exceptions.defaultAuthenticationEntryPointFor(new Http403ForbiddenEntryPoint(), AnyRequestMatcher.INSTANCE);
+			exceptions.defaultAuthenticationEntryPointFor(new Http403ForbiddenEntryPoint(), AnyRequestMatcher.INSTANCE,
+					"FACTOR_X509");
 		}
 	}
 

+ 5 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java

@@ -40,6 +40,7 @@ import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
 import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
 import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
 import org.springframework.security.context.DelegatingApplicationListener;
 import org.springframework.security.core.Authentication;
@@ -372,6 +373,10 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 			http.authenticationProvider(new OidcAuthenticationRequestChecker());
 		}
 		this.initDefaultLoginFilter(http);
+		ExceptionHandlingConfigurer<B> exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class);
+		if (exceptions != null) {
+			exceptions.defaultAuthenticationEntryPointFor(getAuthenticationEntryPoint(), "FACTOR_AUTHORIZATION_CODE");
+		}
 	}
 
 	@Override

+ 4 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java

@@ -259,6 +259,10 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
 		if (authenticationProvider != null) {
 			http.authenticationProvider(authenticationProvider);
 		}
+		ExceptionHandlingConfigurer<H> exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class);
+		if (exceptions != null) {
+			exceptions.defaultAuthenticationEntryPointFor(this.authenticationEntryPoint, "FACTOR_BEARER");
+		}
 	}
 
 	@Override

+ 67 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurer.java

@@ -16,10 +16,15 @@
 
 package org.springframework.security.config.annotation.web.configurers.ott;
 
+import java.io.IOException;
 import java.util.Collections;
 import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
+import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
 
 import org.springframework.context.ApplicationContext;
 import org.springframework.http.HttpMethod;
@@ -35,8 +40,15 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
 import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.context.SecurityContextHolderStrategy;
 import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.FormPostRedirectStrategy;
+import org.springframework.security.web.RedirectStrategy;
 import org.springframework.security.web.authentication.AuthenticationConverter;
 import org.springframework.security.web.authentication.AuthenticationFailureHandler;
 import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@@ -55,6 +67,7 @@ import org.springframework.security.web.csrf.CsrfToken;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
 import org.springframework.util.StringUtils;
+import org.springframework.web.util.UriComponentsBuilder;
 
 /**
  * An {@link AbstractHttpConfigurer} for One-Time Token Login.
@@ -134,6 +147,12 @@ public final class OneTimeTokenLoginConfigurer<H extends HttpSecurityBuilder<H>>
 		AuthenticationProvider authenticationProvider = getAuthenticationProvider();
 		http.authenticationProvider(postProcess(authenticationProvider));
 		intiDefaultLoginFilter(http);
+		ExceptionHandlingConfigurer<H> exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class);
+		if (exceptions != null) {
+			AuthenticationEntryPoint entryPoint = new PostAuthenticationEntryPoint(
+					this.tokenGeneratingUrl + "?username={u}", Map.of("u", Authentication::getName));
+			exceptions.defaultAuthenticationEntryPointFor(entryPoint, "FACTOR_OTT");
+		}
 	}
 
 	private void intiDefaultLoginFilter(H http) {
@@ -391,4 +410,52 @@ public final class OneTimeTokenLoginConfigurer<H extends HttpSecurityBuilder<H>>
 		return this.context;
 	}
 
+	private static final class PostAuthenticationEntryPoint implements AuthenticationEntryPoint {
+
+		private final String entryPointUri;
+
+		private final Map<String, Function<Authentication, String>> params;
+
+		private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
+			.getContextHolderStrategy();
+
+		private RedirectStrategy redirectStrategy = new FormPostRedirectStrategy();
+
+		private PostAuthenticationEntryPoint(String entryPointUri,
+				Map<String, Function<Authentication, String>> params) {
+			this.entryPointUri = entryPointUri;
+			this.params = params;
+		}
+
+		@Override
+		public void commence(HttpServletRequest request, HttpServletResponse response,
+				AuthenticationException authException) throws IOException, ServletException {
+			Authentication authentication = getAuthentication(authException);
+			Assert.notNull(authentication, "could not find authentication in order to perform post");
+			Map<String, String> params = this.params.entrySet()
+				.stream()
+				.collect(Collectors.toMap(Map.Entry::getKey, (entry) -> entry.getValue().apply(authentication)));
+			UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(this.entryPointUri);
+			CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
+			if (csrf != null) {
+				builder.queryParam(csrf.getParameterName(), csrf.getToken());
+			}
+			String entryPointUrl = builder.build(false).expand(params).toUriString();
+			this.redirectStrategy.sendRedirect(request, response, entryPointUrl);
+		}
+
+		private Authentication getAuthentication(AuthenticationException authException) {
+			Authentication authentication = authException.getAuthenticationRequest();
+			if (authentication != null && authentication.isAuthenticated()) {
+				return authentication;
+			}
+			authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
+			if (authentication != null && authentication.isAuthenticated()) {
+				return authentication;
+			}
+			return null;
+		}
+
+	}
+
 }

+ 5 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java

@@ -33,6 +33,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
 import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
 import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
+import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
 import org.springframework.security.saml2.provider.service.authentication.OpenSaml5AuthenticationProvider;
@@ -304,6 +305,10 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 		if (this.authenticationManager == null) {
 			registerDefaultAuthenticationProvider(http);
 		}
+		ExceptionHandlingConfigurer<B> exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class);
+		if (exceptions != null) {
+			exceptions.defaultAuthenticationEntryPointFor(getAuthenticationEntryPoint(), "FACTOR_SAML_RESPONSE");
+		}
 	}
 
 	/**

+ 2 - 1
web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java

@@ -196,7 +196,8 @@ public class ExceptionTranslationFilter extends GenericFilterBean implements Mes
 			}
 			AuthenticationException ex = new InsufficientAuthenticationException(
 					this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication",
-							"Full authentication is required to access this resource"));
+							"Full authentication is required to access this resource"),
+					exception);
 			ex.setAuthenticationRequest(authentication);
 			sendStartAuthentication(request, response, chain, ex);
 		}