From e66c498d8093095c7884ac7ad7ca9c441d920fd1 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:36:09 -0600 Subject: [PATCH] Redirect to Appropriate Entry Point Based on Missing Authorities Issue gh-17934 --- .../ExceptionHandlingConfigurer.java | 233 ++++++++++++------ .../web/configurers/FormLoginConfigurer.java | 4 + .../web/configurers/WebAuthnConfigurer.java | 10 + .../web/configurers/X509Configurer.java | 3 +- .../oauth2/client/OAuth2LoginConfigurer.java | 5 + .../OAuth2ResourceServerConfigurer.java | 4 + .../ott/OneTimeTokenLoginConfigurer.java | 67 +++++ .../saml2/Saml2LoginConfigurer.java | 5 + .../access/ExceptionTranslationFilter.java | 3 +- 9 files changed, 258 insertions(+), 76 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java index 22c9219a72..e88ed02910 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java +++ b/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> private LinkedHashMap defaultDeniedHandlerMappings = new LinkedHashMap<>(); + private Map> entryPoints = new LinkedHashMap<>(); + /** * Creates a new instance * @see HttpSecurity#exceptionHandling(Customizer) @@ -195,6 +189,26 @@ public final class ExceptionHandlingConfigurer> return this; } + public ExceptionHandlingConfigurer defaultAuthenticationEntryPointFor(AuthenticationEntryPoint entryPoint, + RequestMatcher preferredMatcher, String authority) { + this.defaultEntryPointMappings.put(preferredMatcher, entryPoint); + LinkedHashMap byMatcher = this.entryPoints.get(authority); + if (byMatcher == null) { + byMatcher = new LinkedHashMap<>(); + } + byMatcher.put(preferredMatcher, entryPoint); + this.entryPoints.put(authority, byMatcher); + return this; + } + + public ExceptionHandlingConfigurer defaultAuthenticationEntryPointFor(AuthenticationEntryPoint entryPoint, + String authority) { + LinkedHashMap 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> } private AccessDeniedHandler createDefaultDeniedHandler(H http) { + AccessDeniedHandler defaults = createDefaultAccessDeniedHandler(http); + if (this.entryPoints.isEmpty()) { + return defaults; + } + Map deniedHandlers = new LinkedHashMap<>(); + for (Map.Entry> 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 entryPoints = new LinkedHashMap<>(); + for (Map.Entry> entry : this.entryPoints + .entrySet()) { + entryPoints.put(entry.getKey(), entryPointFrom(entry.getValue())); + } + return new AuthenticationFactorDelegatingAuthenticationEntryPoint(entryPoints, defaults); + } + + private AuthenticationEntryPoint entryPointFrom( + LinkedHashMap 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> 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 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 entryPoints; - private final AccessDeniedHandler defaults = new AccessDeniedHandlerImpl(); + private final AuthenticationEntryPoint defaults; + + private AuthenticationFactorDelegatingAuthenticationEntryPoint( + Map 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 needed = authorizationRequest(ex); - if (needed == null) { - this.defaults.handle(request, response, ex); - return; + Collection authorization = authorizationRequest(ex); + entryPoint(authorization).commence(request, response, ex); + } + + private AuthenticationEntryPoint entryPoint(Collection 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 authorizationRequest(AccessDeniedException access) { - if (!(access instanceof AuthorizationDeniedException denied)) { - return null; + private Collection 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> params; + private final ThrowableAnalyzer throwableAnalyzer = new ThrowableAnalyzer(); - private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder - .getContextHolderStrategy(); + private final Map deniedHandlers; - private RedirectStrategy redirectStrategy = new FormPostRedirectStrategy(); + private final AccessDeniedHandler defaults; - private PostAuthenticationEntryPoint(String entryPointUri, - Map> params) { - this.entryPointUri = entryPointUri; - this.params = params; + private AuthenticationFactorDelegatingAccessDeniedHandler(Map 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 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 authorization = authorizationRequest(ex); + deniedHandler(authorization).handle(request, response, ex); + } + + private AccessDeniedHandler deniedHandler(Collection 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 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(); } } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurer.java index 03cf95b390..e3014bcab7 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurer.java @@ -231,6 +231,10 @@ public final class FormLoginConfigurer> extends public void init(H http) throws Exception { super.init(http); initDefaultLoginFilter(http); + ExceptionHandlingConfigurer exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class); + if (exceptions != null) { + exceptions.defaultAuthenticationEntryPointFor(getAuthenticationEntryPoint(), "FACTOR_PASSWORD"); + } } @Override diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java index 7ec3279efb..90538cc79f 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java +++ b/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> return this; } + @Override + public void init(H http) throws Exception { + ExceptionHandlingConfigurer 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) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java index 79a2265962..4b1db122ec 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java @@ -184,7 +184,8 @@ public final class X509Configurer> .setSharedObject(AuthenticationEntryPoint.class, new Http403ForbiddenEntryPoint()); ExceptionHandlingConfigurer exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class); if (exceptions != null) { - exceptions.defaultAuthenticationEntryPointFor(new Http403ForbiddenEntryPoint(), AnyRequestMatcher.INSTANCE); + exceptions.defaultAuthenticationEntryPointFor(new Http403ForbiddenEntryPoint(), AnyRequestMatcher.INSTANCE, + "FACTOR_X509"); } } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java index 494f75109a..e6cea04b6f 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java +++ b/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> http.authenticationProvider(new OidcAuthenticationRequestChecker()); } this.initDefaultLoginFilter(http); + ExceptionHandlingConfigurer exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class); + if (exceptions != null) { + exceptions.defaultAuthenticationEntryPointFor(getAuthenticationEntryPoint(), "FACTOR_AUTHORIZATION_CODE"); + } } @Override diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java index 331730f1db..79c0d8a3aa 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java @@ -259,6 +259,10 @@ public final class OAuth2ResourceServerConfigurer exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class); + if (exceptions != null) { + exceptions.defaultAuthenticationEntryPointFor(this.authenticationEntryPoint, "FACTOR_BEARER"); + } } @Override diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurer.java index 4f01a17e5e..1d04d1f23d 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurer.java +++ b/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> AuthenticationProvider authenticationProvider = getAuthenticationProvider(); http.authenticationProvider(postProcess(authenticationProvider)); intiDefaultLoginFilter(http); + ExceptionHandlingConfigurer 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> return this.context; } + private static final class PostAuthenticationEntryPoint implements AuthenticationEntryPoint { + + private final String entryPointUri; + + private final Map> params; + + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + + private RedirectStrategy redirectStrategy = new FormPostRedirectStrategy(); + + private PostAuthenticationEntryPoint(String entryPointUri, + Map> 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 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; + } + + } + } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java index fbbb3f73a7..93d3d08a5d 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java +++ b/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> if (this.authenticationManager == null) { registerDefaultAuthenticationProvider(http); } + ExceptionHandlingConfigurer exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class); + if (exceptions != null) { + exceptions.defaultAuthenticationEntryPointFor(getAuthenticationEntryPoint(), "FACTOR_SAML_RESPONSE"); + } } /** diff --git a/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java b/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java index 9be0de9478..84a0ba7c26 100644 --- a/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java +++ b/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); }