浏览代码

Revert "Add Saml2LogoutConfigurer"

This reverts commit 6f52baba29fa31c79bbe1b058f1cffe44fb5fab1.
Josh Cummings 4 年之前
父节点
当前提交
4e81bbe386

+ 0 - 137
config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java

@@ -64,7 +64,6 @@ import org.springframework.security.config.annotation.web.configurers.oauth2.cli
 import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
 import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
 import org.springframework.security.config.annotation.web.configurers.openid.OpenIDLoginConfigurer;
 import org.springframework.security.config.annotation.web.configurers.openid.OpenIDLoginConfigurer;
 import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LoginConfigurer;
 import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LoginConfigurer;
-import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LogoutConfigurer;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.context.SecurityContextHolder;
@@ -2122,142 +2121,6 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
 		return HttpSecurity.this;
 		return HttpSecurity.this;
 	}
 	}
 
 
-	/**
-	 * Configures logout support for an SAML 2.0 Relying Party. <br>
-	 * <br>
-	 *
-	 * Implements the <b>Single Logout Profile, using POST and REDIRECT bindings</b>, as
-	 * documented in the
-	 * <a target="_blank" href="https://docs.oasis-open.org/security/saml/">SAML V2.0
-	 * Core,Profiles and Bindings</a> specifications. <br>
-	 * <br>
-	 *
-	 * As a prerequisite to using this feature, is that you have a SAML v2.0 Asserting
-	 * Party to sent a logout request to. The representation of the relying party and the
-	 * asserting party is contained within {@link RelyingPartyRegistration}. <br>
-	 * <br>
-	 *
-	 * {@link RelyingPartyRegistration}(s) are composed within a
-	 * {@link RelyingPartyRegistrationRepository}, which is <b>required</b> and must be
-	 * registered with the {@link ApplicationContext} or configured via
-	 * <code>saml2Logout().relyingPartyRegistrationRepository(..)</code>. <br>
-	 * <br>
-	 *
-	 * The default configuration provides an auto-generated logout endpoint at
-	 * <code>&quot;/saml2/logout&quot;</code> and redirects to <code>/login?logout</code>
-	 * when logout completes. <br>
-	 * <br>
-	 *
-	 * <p>
-	 * <h2>Example Configuration</h2>
-	 *
-	 * The following example shows the minimal configuration required, using SimpleSamlPhp
-	 * as the asserting party.
-	 *
-	 * <pre>
-	 *	&#064;EnableWebSecurity
-	 *	&#064;Configuration
-	 *	public class Saml2LogoutSecurityConfig {
-	 *		&#064;Bean
-	 *		public SecurityFilterChain web(HttpSecurity http) throws Exception {
-	 *			http
-	 *				.authorizeRequests((authorize) -> authorize
-	 *					.anyRequest().authenticated()
-	 *				)
-	 *				.saml2Login(withDefaults())
-	 *				.saml2Logout(withDefaults());
-	 *			return http.build();
-	 *		}
-	 *
-	 *		&#064;Bean
-	 *		public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
-	 *			RelyingPartyRegistration registration = RelyingPartyRegistrations
-	 *					.withMetadataLocation("https://ap.example.org/metadata")
-	 *					.registrationId("simple")
-	 *					.build();
-	 *			return new InMemoryRelyingPartyRegistrationRepository(registration);
-	 *		}
-	 *	}
-	 * </pre>
-	 *
-	 * <p>
-	 * @return the {@link Saml2LoginConfigurer} for further customizations
-	 * @throws Exception
-	 * @since 5.5
-	 */
-	public HttpSecurity saml2Logout(Customizer<Saml2LogoutConfigurer<HttpSecurity>> saml2LogoutCustomizer)
-			throws Exception {
-		saml2LogoutCustomizer.customize(getOrApply(new Saml2LogoutConfigurer<>(getContext())));
-		return HttpSecurity.this;
-	}
-
-	/**
-	 * Configures logout support for an SAML 2.0 Relying Party. <br>
-	 * <br>
-	 *
-	 * Implements the <b>Single Logout Profile, using POST and REDIRECT bindings</b>, as
-	 * documented in the
-	 * <a target="_blank" href="https://docs.oasis-open.org/security/saml/">SAML V2.0
-	 * Core,Profiles and Bindings</a> specifications. <br>
-	 * <br>
-	 *
-	 * As a prerequisite to using this feature, is that you have a SAML v2.0 Asserting
-	 * Party to sent a logout request to. The representation of the relying party and the
-	 * asserting party is contained within {@link RelyingPartyRegistration}. <br>
-	 * <br>
-	 *
-	 * {@link RelyingPartyRegistration}(s) are composed within a
-	 * {@link RelyingPartyRegistrationRepository}, which is <b>required</b> and must be
-	 * registered with the {@link ApplicationContext} or configured via
-	 * <code>saml2Logout().relyingPartyRegistrationRepository(..)</code>. <br>
-	 * <br>
-	 *
-	 * The default configuration provides an auto-generated logout endpoint at
-	 * <code>&quot;/saml2/logout&quot;</code> and redirects to <code>/login?logout</code>
-	 * when logout completes. <br>
-	 * <br>
-	 *
-	 * <p>
-	 * <h2>Example Configuration</h2>
-	 *
-	 * The following example shows the minimal configuration required, using SimpleSamlPhp
-	 * as the asserting party.
-	 *
-	 * <pre>
-	 *	&#064;EnableWebSecurity
-	 *	&#064;Configuration
-	 *	public class Saml2LogoutSecurityConfig {
-	 *		&#064;Bean
-	 *		public SecurityFilterChain web(HttpSecurity http) throws Exception {
-	 *			http
-	 *				.authorizeRequests((authorize) -> authorize
-	 *					.anyRequest().authenticated()
-	 *				)
-	 *				.saml2Login(withDefaults())
-	 *				.saml2Logout(withDefaults());
-	 *			return http.build();
-	 *		}
-	 *
-	 *		&#064;Bean
-	 *		public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
-	 *			RelyingPartyRegistration registration = RelyingPartyRegistrations
-	 *					.withMetadataLocation("https://ap.example.org/metadata")
-	 *					.registrationId("simple")
-	 *					.build();
-	 *			return new InMemoryRelyingPartyRegistrationRepository(registration);
-	 *		}
-	 *	}
-	 * </pre>
-	 *
-	 * <p>
-	 * @return the {@link Saml2LoginConfigurer} for further customizations
-	 * @throws Exception
-	 * @since 5.5
-	 */
-	public Saml2LogoutConfigurer<HttpSecurity> saml2Logout() throws Exception {
-		return getOrApply(new Saml2LogoutConfigurer<>(getContext()));
-	}
-
 	/**
 	/**
 	 * Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0
 	 * Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0
 	 * Provider. <br>
 	 * Provider. <br>

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

@@ -221,10 +221,6 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 				// Setup auto-redirect to provider login page
 				// Setup auto-redirect to provider login page
 				// when only 1 IDP is configured
 				// when only 1 IDP is configured
 				this.updateAuthenticationDefaults();
 				this.updateAuthenticationDefaults();
-				Saml2LogoutConfigurer<B> logoutConfigurer = http.getConfigurer(Saml2LogoutConfigurer.class);
-				if (logoutConfigurer != null && !logoutConfigurer.isCustomLogoutSuccess()) {
-					logoutConfigurer.logoutSuccessUrl("/login?logout");
-				}
 				this.updateAccessDefaults(http);
 				this.updateAccessDefaults(http);
 				String loginUrl = providerUrlMap.entrySet().iterator().next().getKey();
 				String loginUrl = providerUrlMap.entrySet().iterator().next().getKey();
 				final LoginUrlAuthenticationEntryPoint entryPoint = new LoginUrlAuthenticationEntryPoint(loginUrl);
 				final LoginUrlAuthenticationEntryPoint entryPoint = new LoginUrlAuthenticationEntryPoint(loginUrl);

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

@@ -1,637 +0,0 @@
-/*
- * Copyright 2002-2021 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.security.config.annotation.web.configurers.saml2;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-import org.opensaml.core.Version;
-
-import org.springframework.context.ApplicationContext;
-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.config.annotation.web.configurers.AbstractHttpConfigurer;
-import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
-import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
-import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
-import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml3LogoutRequestResolver;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml3LogoutResponseResolver;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestResolver;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseResolver;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSamlLogoutRequestHandler;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSamlLogoutResponseHandler;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestRepository;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestSuccessHandler;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseFilter;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseSuccessHandler;
-import org.springframework.security.web.authentication.logout.CompositeLogoutHandler;
-import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
-import org.springframework.security.web.authentication.logout.LogoutFilter;
-import org.springframework.security.web.authentication.logout.LogoutHandler;
-import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler;
-import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
-import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
-import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
-import org.springframework.security.web.csrf.CsrfLogoutHandler;
-import org.springframework.security.web.csrf.CsrfTokenRepository;
-import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
-import org.springframework.security.web.util.matcher.OrRequestMatcher;
-import org.springframework.security.web.util.matcher.RequestMatcher;
-import org.springframework.util.Assert;
-
-/**
- * Adds SAML 2.0 logout support.
- *
- * <h2>Security Filters</h2>
- *
- * The following Filters are populated
- *
- * <ul>
- * <li>{@link LogoutFilter}</li>
- * <li>{@link Saml2LogoutRequestFilter}</li>
- * <li>{@link Saml2LogoutResponseFilter}</li>
- * </ul>
- *
- * <p>
- * The following configuration options are available:
- *
- * <ul>
- * <li>{@link #logoutUrl} - The URL to initiate SAML 2.0 Logout</li>
- * <li>{@link #logoutRequestMatcher} - The {@link RequestMatcher} to initiate SAML 2.0
- * Logout</li>
- * <li>{@link #logoutSuccessHandler} - The {@link LogoutSuccessHandler} to execute once
- * SAML 2.0 Logout is complete</li>
- * <li>{@link LogoutRequestConfigurer#logoutRequestMatcher} - The {@link RequestMatcher}
- * to receive SAML 2.0 Logout Requests</li>
- * <li>{@link LogoutRequestConfigurer#logoutHandler} - The {@link LogoutHandler} for
- * processing SAML 2.0 Logout Requests</li>
- * <li>{@link LogoutRequestConfigurer#logoutRequestResolver} - The
- * {@link Saml2LogoutRequestResolver} for creating SAML 2.0 Logout Requests</li>
- * <li>{@link LogoutRequestConfigurer#logoutRequestRepository} - The
- * {@link Saml2LogoutRequestRepository} for storing SAML 2.0 Logout Requests</li>
- * <li>{@link LogoutResponseConfigurer#logoutRequestMatcher} - The {@link RequestMatcher}
- * to receive SAML 2.0 Logout Responses</li>
- * <li>{@link LogoutResponseConfigurer#logoutHandler} - The {@link LogoutHandler} for
- * processing SAML 2.0 Logout Responses</li>
- * <li>{@link LogoutResponseConfigurer#logoutResponseResolver} - The
- * {@link Saml2LogoutResponseResolver} for creating SAML 2.0 Logout Responses</li>
- * </ul>
- *
- * <h2>Shared Objects Created</h2>
- *
- * No shared Objects are created
- *
- * <h2>Shared Objects Used</h2>
- *
- * Uses {@link CsrfTokenRepository} to add the {@link CsrfLogoutHandler}.
- *
- * @author Josh Cummings
- * @since 5.5
- * @see Saml2LogoutConfigurer
- */
-public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
-		extends AbstractHttpConfigurer<Saml2LogoutConfigurer<H>, H> {
-
-	private ApplicationContext context;
-
-	private List<LogoutHandler> logoutHandlers = new ArrayList<>();
-
-	private SecurityContextLogoutHandler contextLogoutHandler = new SecurityContextLogoutHandler();
-
-	private String logoutSuccessUrl = "/login?logout";
-
-	private LogoutSuccessHandler logoutSuccessHandler;
-
-	private String logoutUrl = "/logout";
-
-	private RequestMatcher logoutRequestMatcher;
-
-	private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
-
-	private LogoutRequestConfigurer logoutRequestConfigurer;
-
-	private LogoutResponseConfigurer logoutResponseConfigurer;
-
-	/**
-	 * Creates a new instance
-	 * @see HttpSecurity#logout()
-	 */
-	public Saml2LogoutConfigurer(ApplicationContext context) {
-		this.context = context;
-		this.logoutRequestConfigurer = new LogoutRequestConfigurer();
-		this.logoutResponseConfigurer = new LogoutResponseConfigurer(this.logoutRequestConfigurer);
-	}
-
-	/**
-	 * Adds a {@link LogoutHandler}. {@link SecurityContextLogoutHandler} and
-	 * {@link LogoutSuccessEventPublishingLogoutHandler} are added as last
-	 * {@link LogoutHandler} instances by default.
-	 * @param logoutHandler the {@link LogoutHandler} to add
-	 * @return the {@link Saml2LogoutConfigurer} for further customization
-	 */
-	public Saml2LogoutConfigurer<H> addLogoutHandler(LogoutHandler logoutHandler) {
-		Assert.notNull(logoutHandler, "logoutHandler cannot be null");
-		this.logoutHandlers.add(logoutHandler);
-		return this;
-	}
-
-	/**
-	 * Specifies if {@link SecurityContextLogoutHandler} should clear the
-	 * {@link Authentication} at the time of logout.
-	 * @param clearAuthentication true {@link SecurityContextLogoutHandler} should clear
-	 * the {@link Authentication} (default), or false otherwise.
-	 * @return the {@link Saml2LogoutConfigurer} for further customization
-	 */
-	public Saml2LogoutConfigurer<H> clearAuthentication(boolean clearAuthentication) {
-		this.contextLogoutHandler.setClearAuthentication(clearAuthentication);
-		return this;
-	}
-
-	/**
-	 * Configures {@link SecurityContextLogoutHandler} to invalidate the
-	 * {@link HttpSession} at the time of logout.
-	 * @param invalidateHttpSession true if the {@link HttpSession} should be invalidated
-	 * (default), or false otherwise.
-	 * @return the {@link Saml2LogoutConfigurer} for further customization
-	 */
-	public Saml2LogoutConfigurer<H> invalidateHttpSession(boolean invalidateHttpSession) {
-		this.contextLogoutHandler.setInvalidateHttpSession(invalidateHttpSession);
-		return this;
-	}
-
-	/**
-	 * The URL that triggers log out to occur (default is "/logout"). If CSRF protection
-	 * is enabled (default), then the request must also be a POST. This means that by
-	 * default POST "/logout" is required to trigger a log out. If CSRF protection is
-	 * disabled, then any HTTP method is allowed.
-	 *
-	 * <p>
-	 * It is considered best practice to use an HTTP POST on any action that changes state
-	 * (i.e. log out) to protect against
-	 * <a href="https://en.wikipedia.org/wiki/Cross-site_request_forgery">CSRF
-	 * attacks</a>. If you really want to use an HTTP GET, you can use
-	 * <code>logoutRequestMatcher(new AntPathRequestMatcher(logoutUrl, "GET"));</code>
-	 * </p>
-	 * @param logoutUrl the URL that will invoke logout.
-	 * @return the {@link Saml2LogoutConfigurer} for further customization
-	 * @see #logoutRequestMatcher(RequestMatcher)
-	 * @see HttpSecurity#csrf()
-	 */
-	public Saml2LogoutConfigurer<H> logoutUrl(String logoutUrl) {
-		this.logoutRequestMatcher = null;
-		this.logoutUrl = logoutUrl;
-		return this;
-	}
-
-	/**
-	 * The RequestMatcher that triggers log out to occur. In most circumstances users will
-	 * use {@link #logoutUrl(String)} which helps enforce good practices.
-	 * @param logoutRequestMatcher the RequestMatcher used to determine if logout should
-	 * occur.
-	 * @return the {@link Saml2LogoutConfigurer} for further customization
-	 * @see #logoutUrl(String)
-	 */
-	public Saml2LogoutConfigurer<H> logoutRequestMatcher(RequestMatcher logoutRequestMatcher) {
-		this.logoutUrl = null;
-		this.logoutRequestMatcher = logoutRequestMatcher;
-		return this;
-	}
-
-	/**
-	 * The URL to redirect to after logout has occurred. The default is "/login?logout".
-	 * This is a shortcut for invoking {@link #logoutSuccessHandler(LogoutSuccessHandler)}
-	 * with a {@link SimpleUrlLogoutSuccessHandler}.
-	 * @param logoutSuccessUrl the URL to redirect to after logout occurred
-	 * @return the {@link Saml2LogoutConfigurer} for further customization
-	 */
-	public Saml2LogoutConfigurer<H> logoutSuccessUrl(String logoutSuccessUrl) {
-		SimpleUrlLogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
-		logoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
-		this.logoutSuccessHandler = logoutSuccessHandler;
-		return this;
-	}
-
-	/**
-	 * Sets the {@link LogoutSuccessHandler} to use. If this is specified,
-	 * {@link #logoutSuccessUrl(String)} is ignored.
-	 * @param logoutSuccessHandler the {@link LogoutSuccessHandler} to use after a user
-	 * has been logged out.
-	 * @return the {@link Saml2LogoutConfigurer} for further customizations
-	 */
-	public Saml2LogoutConfigurer<H> logoutSuccessHandler(LogoutSuccessHandler logoutSuccessHandler) {
-		this.logoutSuccessHandler = logoutSuccessHandler;
-		return this;
-	}
-
-	/**
-	 * Allows specifying the names of cookies to be removed on logout success. This is a
-	 * shortcut to easily invoke {@link #addLogoutHandler(LogoutHandler)} with a
-	 * {@link CookieClearingLogoutHandler}.
-	 * @param cookieNamesToClear the names of cookies to be removed on logout success.
-	 * @return the {@link Saml2LogoutConfigurer} for further customization
-	 */
-	public Saml2LogoutConfigurer<H> deleteCookies(String... cookieNamesToClear) {
-		return addLogoutHandler(new CookieClearingLogoutHandler(cookieNamesToClear));
-	}
-
-	/**
-	 * Sets the {@code RelyingPartyRegistrationRepository} of relying parties, each party
-	 * representing a service provider, SP and this host, and identity provider, IDP pair
-	 * that communicate with each other.
-	 * @param repo the repository of relying parties
-	 * @return the {@link Saml2LoginConfigurer} for further configuration
-	 */
-	public Saml2LogoutConfigurer<H> relyingPartyRegistrationRepository(RelyingPartyRegistrationRepository repo) {
-		this.relyingPartyRegistrationRepository = repo;
-		return this;
-	}
-
-	/**
-	 * Get configurer for SAML 2.0 Logout Request components
-	 * @return the {@link LogoutRequestConfigurer} for further customizations
-	 */
-	public LogoutRequestConfigurer logoutRequest() {
-		return this.logoutRequestConfigurer;
-	}
-
-	/**
-	 * Configures SAML 2.0 Logout Request components
-	 * @param logoutRequestConfigurerCustomizer the {@link Customizer} to provide more
-	 * options for the {@link LogoutRequestConfigurer}
-	 * @return the {@link Saml2LogoutConfigurer} for further customizations
-	 */
-	public Saml2LogoutConfigurer<H> logoutRequest(
-			Customizer<LogoutRequestConfigurer> logoutRequestConfigurerCustomizer) {
-		logoutRequestConfigurerCustomizer.customize(this.logoutRequestConfigurer);
-		return this;
-	}
-
-	/**
-	 * Get configurer for SAML 2.0 Logout Response components
-	 * @return the {@link LogoutResponseConfigurer} for further customizations
-	 */
-	public LogoutResponseConfigurer logoutResponse() {
-		return this.logoutResponseConfigurer;
-	}
-
-	/**
-	 * Configures SAML 2.0 Logout Request components
-	 * @param logoutResponseConfigurerCustomizer the {@link Customizer} to provide more
-	 * options for the {@link LogoutResponseConfigurer}
-	 * @return the {@link Saml2LogoutConfigurer} for further customizations
-	 */
-	public Saml2LogoutConfigurer<H> logoutResponse(
-			Customizer<LogoutResponseConfigurer> logoutResponseConfigurerCustomizer) {
-		logoutResponseConfigurerCustomizer.customize(this.logoutResponseConfigurer);
-		return this;
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-	@Override
-	public void configure(H http) throws Exception {
-		RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = getRelyingPartyRegistrationResolver(http);
-		CsrfTokenRepository csrfTokenRepository = http.getSharedObject(CsrfTokenRepository.class);
-		if (csrfTokenRepository != null) {
-			this.logoutHandlers.add(new CsrfLogoutHandler(csrfTokenRepository));
-		}
-		this.logoutHandlers.add(this.contextLogoutHandler);
-		this.logoutHandlers.add(postProcess(new LogoutSuccessEventPublishingLogoutHandler()));
-		LogoutFilter logoutFilter = createLogoutFilter(http, this.logoutHandlers, relyingPartyRegistrationResolver);
-		http.addFilterBefore(logoutFilter, LogoutFilter.class);
-		Saml2LogoutRequestFilter logoutRequestFilter = createLogoutRequestFilter(this.logoutHandlers,
-				relyingPartyRegistrationResolver);
-		http.addFilterBefore(logoutRequestFilter, LogoutFilter.class);
-		Saml2LogoutResponseFilter logoutResponseFilter = createLogoutResponseFilter(relyingPartyRegistrationResolver);
-		logoutResponseFilter.setLogoutSuccessHandler(getLogoutSuccessHandler());
-		http.addFilterBefore(logoutResponseFilter, LogoutFilter.class);
-	}
-
-	/**
-	 * Returns true if the logout success has been customized via
-	 * {@link #logoutSuccessUrl(String)} or
-	 * {@link #logoutSuccessHandler(LogoutSuccessHandler)}.
-	 * @return true if logout success handling has been customized, else false
-	 */
-	boolean isCustomLogoutSuccess() {
-		return this.logoutSuccessHandler != null;
-	}
-
-	private RelyingPartyRegistrationResolver getRelyingPartyRegistrationResolver(H http) {
-		RelyingPartyRegistrationRepository registrations = getRelyingPartyRegistrationRepository();
-		return new DefaultRelyingPartyRegistrationResolver(registrations);
-	}
-
-	private RelyingPartyRegistrationRepository getRelyingPartyRegistrationRepository() {
-		if (this.relyingPartyRegistrationRepository == null) {
-			this.relyingPartyRegistrationRepository = getBeanOrNull(RelyingPartyRegistrationRepository.class);
-		}
-		return this.relyingPartyRegistrationRepository;
-	}
-
-	private LogoutFilter createLogoutFilter(H http, List<LogoutHandler> logoutHandlers,
-			RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
-		LogoutHandler[] handlers = logoutHandlers.toArray(new LogoutHandler[0]);
-		LogoutSuccessHandler logoutRequestSuccessHandler = this.logoutRequestConfigurer
-				.logoutRequestSuccessHandler(relyingPartyRegistrationResolver);
-		LogoutSuccessHandler finalSuccessHandler = getLogoutSuccessHandler();
-		LogoutSuccessHandler logoutSuccessHandler = (request, response, authentication) -> {
-			if (authentication == null) {
-				finalSuccessHandler.onLogoutSuccess(request, response, authentication);
-			}
-			else {
-				logoutRequestSuccessHandler.onLogoutSuccess(request, response, authentication);
-			}
-		};
-		LogoutFilter result = new LogoutFilter(logoutSuccessHandler, handlers) {
-			@Override
-			protected boolean requiresLogout(HttpServletRequest request, HttpServletResponse response) {
-				Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-				if (!(authentication instanceof Saml2Authentication)) {
-					return false;
-				}
-				return super.requiresLogout(request, response);
-			}
-		};
-		result.setLogoutRequestMatcher(getLogoutRequestMatcher(http));
-		return postProcess(result);
-	}
-
-	private Saml2LogoutRequestFilter createLogoutRequestFilter(List<LogoutHandler> logoutHandlers,
-			RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
-		LogoutHandler logoutRequestHandler = this.logoutRequestConfigurer
-				.logoutRequestHandler(relyingPartyRegistrationResolver);
-		List<LogoutHandler> handlers = new ArrayList<>();
-		handlers.add(logoutRequestHandler);
-		handlers.addAll(logoutHandlers);
-		Saml2LogoutRequestFilter logoutRequestFilter = new Saml2LogoutRequestFilter(
-				this.logoutResponseConfigurer.logoutResponseSuccessHandler(relyingPartyRegistrationResolver),
-				new CompositeLogoutHandler(handlers));
-		logoutRequestFilter.setLogoutRequestMatcher(this.logoutRequestConfigurer.requestMatcher);
-		CsrfConfigurer<H> csrf = getBuilder().getConfigurer(CsrfConfigurer.class);
-		if (csrf != null) {
-			csrf.ignoringRequestMatchers(this.logoutRequestConfigurer.requestMatcher);
-		}
-		return logoutRequestFilter;
-	}
-
-	private Saml2LogoutResponseFilter createLogoutResponseFilter(
-			RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
-		Saml2LogoutResponseFilter logoutResponseFilter = new Saml2LogoutResponseFilter(
-				this.logoutResponseConfigurer.logoutResponseHandler(relyingPartyRegistrationResolver));
-		logoutResponseFilter.setLogoutRequestMatcher(this.logoutResponseConfigurer.requestMatcher);
-		CsrfConfigurer<H> csrf = getBuilder().getConfigurer(CsrfConfigurer.class);
-		if (csrf != null) {
-			csrf.ignoringRequestMatchers(this.logoutResponseConfigurer.requestMatcher);
-		}
-		logoutResponseFilter.setLogoutSuccessHandler(getLogoutSuccessHandler());
-		return logoutResponseFilter;
-	}
-
-	private RequestMatcher getLogoutRequestMatcher(H http) {
-		if (this.logoutRequestMatcher != null) {
-			return this.logoutRequestMatcher;
-		}
-		this.logoutRequestMatcher = createLogoutRequestMatcher(http);
-		return this.logoutRequestMatcher;
-	}
-
-	@SuppressWarnings("unchecked")
-	private RequestMatcher createLogoutRequestMatcher(H http) {
-		RequestMatcher post = createLogoutRequestMatcher("POST");
-		if (http.getConfigurer(CsrfConfigurer.class) != null) {
-			return post;
-		}
-		RequestMatcher get = createLogoutRequestMatcher("GET");
-		return new OrRequestMatcher(get, post);
-	}
-
-	private RequestMatcher createLogoutRequestMatcher(String httpMethod) {
-		return new AntPathRequestMatcher(this.logoutUrl, httpMethod);
-	}
-
-	private LogoutSuccessHandler getLogoutSuccessHandler() {
-		if (this.logoutSuccessHandler != null) {
-			return this.logoutSuccessHandler;
-		}
-		SimpleUrlLogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
-		logoutSuccessHandler.setDefaultTargetUrl(this.logoutSuccessUrl);
-		this.logoutSuccessHandler = logoutSuccessHandler;
-		return logoutSuccessHandler;
-	}
-
-	private <C> C getBeanOrNull(Class<C> clazz) {
-		if (this.context == null) {
-			return null;
-		}
-		if (this.context.getBeanNamesForType(clazz).length == 0) {
-			return null;
-		}
-		return this.context.getBean(clazz);
-	}
-
-	/**
-	 * A configurer for SAML 2.0 LogoutRequest components
-	 */
-	public final class LogoutRequestConfigurer {
-
-		private RequestMatcher requestMatcher = new AntPathRequestMatcher("/logout/saml2/slo");
-
-		private LogoutHandler logoutHandler;
-
-		private LogoutSuccessHandler logoutSuccessHandler;
-
-		private Saml2LogoutRequestRepository logoutRequestRepository = new HttpSessionLogoutRequestRepository();
-
-		LogoutRequestConfigurer() {
-		}
-
-		/**
-		 * Use this {@link RequestMatcher} for recognizing a logout request from the
-		 * asserting party
-		 *
-		 * <p>
-		 * Defaults to {@code /logout/saml2}
-		 * @param requestMatcher the {@link RequestMatcher} to use
-		 * @return the {@link LogoutRequestConfigurer} for further customizations
-		 */
-		public LogoutRequestConfigurer logoutRequestMatcher(RequestMatcher requestMatcher) {
-			this.requestMatcher = requestMatcher;
-			return this;
-		}
-
-		/**
-		 * Use this {@link LogoutHandler} for processing a logout request from the
-		 * asserting party
-		 * @param logoutHandler the {@link LogoutHandler} to use
-		 * @return the {@link LogoutRequestConfigurer} for further customizations
-		 */
-		public LogoutRequestConfigurer logoutRequestHandler(LogoutHandler logoutHandler) {
-			this.logoutHandler = logoutHandler;
-			return this;
-		}
-
-		/**
-		 * Use this {@link Saml2LogoutRequestResolver} for producing a logout request to
-		 * send to the asserting party
-		 * @param logoutRequestResolver the {@link Saml2LogoutRequestResolver} to use
-		 * @return the {@link LogoutRequestConfigurer} for further customizations
-		 */
-		public LogoutRequestConfigurer logoutRequestResolver(Saml2LogoutRequestResolver logoutRequestResolver) {
-			this.logoutSuccessHandler = new Saml2LogoutRequestSuccessHandler(logoutRequestResolver);
-			return this;
-		}
-
-		/**
-		 * Use this {@link Saml2LogoutRequestRepository} for storing logout requests
-		 * @param logoutRequestRepository the {@link Saml2LogoutRequestRepository} to use
-		 * @return the {@link LogoutRequestConfigurer} for further customizations
-		 */
-		public LogoutRequestConfigurer logoutRequestRepository(Saml2LogoutRequestRepository logoutRequestRepository) {
-			this.logoutRequestRepository = logoutRequestRepository;
-			return this;
-		}
-
-		public Saml2LogoutConfigurer<H> and() {
-			return Saml2LogoutConfigurer.this;
-		}
-
-		private LogoutHandler logoutRequestHandler(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
-			if (this.logoutHandler == null) {
-				return new OpenSamlLogoutRequestHandler(relyingPartyRegistrationResolver);
-			}
-			return this.logoutHandler;
-		}
-
-		private LogoutSuccessHandler logoutRequestSuccessHandler(
-				RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
-			if (this.logoutSuccessHandler == null) {
-				Saml2LogoutRequestSuccessHandler logoutSuccessHandler = new Saml2LogoutRequestSuccessHandler(
-						logoutRequestResolver(relyingPartyRegistrationResolver));
-				logoutSuccessHandler.setLogoutRequestRepository(this.logoutRequestRepository);
-				return logoutSuccessHandler;
-			}
-			return this.logoutSuccessHandler;
-		}
-
-		private Saml2LogoutRequestResolver logoutRequestResolver(
-				RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
-			if (Version.getVersion().startsWith("4")) {
-				return new OpenSaml4LogoutRequestResolver(relyingPartyRegistrationResolver);
-			}
-			return new OpenSaml3LogoutRequestResolver(relyingPartyRegistrationResolver);
-		}
-
-	}
-
-	public final class LogoutResponseConfigurer {
-
-		private final LogoutRequestConfigurer logoutRequest;
-
-		private RequestMatcher requestMatcher = new AntPathRequestMatcher("/logout/saml2/slo");
-
-		private LogoutHandler logoutHandler;
-
-		private LogoutSuccessHandler logoutSuccessHandler;
-
-		LogoutResponseConfigurer(LogoutRequestConfigurer logoutRequest) {
-			this.logoutRequest = logoutRequest;
-		}
-
-		/**
-		 * Use this {@link RequestMatcher} for recognizing a logout response from the
-		 * asserting party
-		 *
-		 * <p>
-		 * Defaults to {@code /logout/saml2}
-		 * @param requestMatcher the {@link RequestMatcher} to use
-		 * @return the {@link LogoutRequestConfigurer} for further customizations
-		 */
-		public LogoutResponseConfigurer logoutRequestMatcher(RequestMatcher requestMatcher) {
-			this.requestMatcher = requestMatcher;
-			return this;
-		}
-
-		/**
-		 * Use this {@link LogoutHandler} for processing a logout response from the
-		 * asserting party
-		 * @param logoutHandler the {@link LogoutHandler} to use
-		 * @return the {@link LogoutRequestConfigurer} for further customizations
-		 */
-		public LogoutResponseConfigurer logoutResponseHandler(LogoutHandler logoutHandler) {
-			this.logoutHandler = logoutHandler;
-			return this;
-		}
-
-		/**
-		 * Use this {@link Saml2LogoutRequestResolver} for producing a logout response to
-		 * send to the asserting party
-		 * @param logoutResponseResolver the {@link Saml2LogoutResponseResolver} to use
-		 * @return the {@link LogoutRequestConfigurer} for further customizations
-		 */
-		public LogoutResponseConfigurer logoutResponseResolver(Saml2LogoutResponseResolver logoutResponseResolver) {
-			this.logoutSuccessHandler = new Saml2LogoutResponseSuccessHandler(logoutResponseResolver);
-			return this;
-		}
-
-		public Saml2LogoutConfigurer<H> and() {
-			return Saml2LogoutConfigurer.this;
-		}
-
-		private LogoutHandler logoutResponseHandler(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
-			if (this.logoutHandler == null) {
-				OpenSamlLogoutResponseHandler logoutHandler = new OpenSamlLogoutResponseHandler(
-						relyingPartyRegistrationResolver);
-				logoutHandler.setLogoutRequestRepository(this.logoutRequest.logoutRequestRepository);
-				return logoutHandler;
-			}
-			return this.logoutHandler;
-		}
-
-		private LogoutSuccessHandler logoutResponseSuccessHandler(
-				RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
-			if (this.logoutSuccessHandler == null) {
-				return new Saml2LogoutResponseSuccessHandler(logoutResponseResolver(relyingPartyRegistrationResolver));
-			}
-			return this.logoutSuccessHandler;
-		}
-
-		private Saml2LogoutResponseResolver logoutResponseResolver(
-				RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
-			if (Version.getVersion().startsWith("4")) {
-				return new OpenSaml4LogoutResponseResolver(relyingPartyRegistrationResolver);
-			}
-			return new OpenSaml3LogoutResponseResolver(relyingPartyRegistrationResolver);
-		}
-
-	}
-
-}

+ 0 - 400
config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurerTests.java

@@ -1,400 +0,0 @@
-/*
- * Copyright 2002-2021 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.security.config.annotation.web.configurers.saml2;
-
-import java.time.Instant;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.function.Consumer;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.opensaml.saml.saml2.core.LogoutRequest;
-import org.opensaml.xmlsec.signature.support.SignatureConstants;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Import;
-import org.springframework.mock.web.MockFilterChain;
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.mock.web.MockHttpServletResponse;
-import org.springframework.mock.web.MockHttpSession;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.config.test.SpringTestRule;
-import org.springframework.security.core.authority.AuthorityUtils;
-import org.springframework.security.saml2.Saml2Exception;
-import org.springframework.security.saml2.core.Saml2X509Credential;
-import org.springframework.security.saml2.core.TestSaml2X509Credentials;
-import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal;
-import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
-import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects;
-import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest;
-import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse;
-import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
-import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
-import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
-import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestRepository;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver;
-import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver.Saml2LogoutResponseBuilder;
-import org.springframework.security.web.SecurityFilterChain;
-import org.springframework.security.web.authentication.logout.LogoutHandler;
-import org.springframework.test.web.servlet.MockMvc;
-import org.springframework.test.web.servlet.MvcResult;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.BDDMockito.RETURNS_SELF;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.BDDMockito.mock;
-import static org.mockito.BDDMockito.reset;
-import static org.mockito.BDDMockito.verify;
-import static org.mockito.BDDMockito.verifyNoInteractions;
-import static org.mockito.BDDMockito.willAnswer;
-import static org.mockito.BDDMockito.willReturn;
-import static org.springframework.security.config.Customizer.withDefaults;
-import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
-import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
-/**
- * Tests for different Java configuration for {@link Saml2LogoutConfigurer}
- */
-public class Saml2LogoutConfigurerTests {
-
-	@Autowired
-	private ConfigurableApplicationContext context;
-
-	@Autowired
-	private RelyingPartyRegistrationRepository repository;
-
-	private final Saml2LogoutRequestRepository logoutRequestRepository = new HttpSessionLogoutRequestRepository();
-
-	@Rule
-	public final SpringTestRule spring = new SpringTestRule();
-
-	@Autowired(required = false)
-	MockMvc mvc;
-
-	private Saml2Authentication user = new Saml2Authentication(
-			new DefaultSaml2AuthenticatedPrincipal("user", Collections.emptyMap()), "response",
-			AuthorityUtils.createAuthorityList("ROLE_USER"), "registration-id");
-
-	String apLogoutRequest = "nZFBa4MwGIb/iuQeE2NTXFDLQAaC26Hrdtgt1dQFNMnyxdH9+zlboeyww275SN7nzcOX787jEH0qD9qaAiUxRZEyre206Qv0cnjAGdqVOchxYE40trdT2KuPSUGI5qQBcbkq0OSNsBI0CCNHBSK04vn+sREspsJ5G2xrBxRVc1AbGZa29xAcCEK8i9VZjm5QsfU9GZYWsoCJv5ShqK4K1Ow5p5LyU4aP6XaLN3cpw9mGctydjrxNaZt1XM5vASZVGwjShAIxyhJMU8z4gSWCM8GSmDH+hqLX1Xv+JLpaiiXsb+3+lpMAyv8IoVI6rEzQ4QvrLie3uBX+NMfr6l/waT6t0AumvI6/FlN+Aw==";
-
-	String apLogoutRequestSigAlg = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256;
-
-	String apLogoutRequestRelayState = "33591874-b123-4f2c-ab0d-2d0d84aa8b56";
-
-	String apLogoutRequestSignature = "oKqdzrmn2YAqXcwkow2lzRXr5PNHm0s/gWsRnaZYhC+Oq5ekK5uIKQYvtmNR94HJjDe1VRs+vVQCYivgdoTzBV2ZlffTXZmYsCsY9q4jbCWR6R5CbhU73/MkKQsPcyVvMhNYxnDYapIlxDsfoZNTboDEz3GM+HRoGRfl9emCXY0lPRYwqC4kpu7oMDBkafR0A09jPIxFuNpqlLPwUxL9m+DGkvDK3mFDN1xJcgZaK73HcuJe7Qh4huOrKNFetwc5EvqfiwgiWF6sfq9A+rZBfCIYo10NNLY7fNQAR2IqwcKtawHgTGWbeshRyFrwVYMR64EnClfxUHsHKf5kiZ2dlw==";
-
-	String apLogoutResponse = "fZHRa4MwEMb/Fcl7jEadGqplrAwK3Uvb9WFvZ4ydoInk4uj++1nXbmWMvhwcd9/3Jb9bLE99530oi63RBQn9gHhKS1O3+liQ1/0zzciyXCD0HR/ExhzN6LYKB6NReZNUo/ieFWS0WhjAFoWGXqFwUuweXzaC+4EYrHFGmo54K4Wu1eDmuHfnBhSM2cFXJ+iHTvnGHlk3x7DZmNlLGvHWq4Jstk0GUSjjiIZJI2lcpQnNeRLTAOo4fwCeQg3Trr6+cm/OqmnWVHECVGWQ0jgCSatsKvXUxhFvZF7xSYU4qrVGB9oVhAc8pEFEebLnkeBc8NyPePpGvMOV1/Q3cqEjZrG9hXKfCSAqe+ZAShio0q51n7StF+zW7gf9zoEb8U/7ZGrlHaAb1f0onLfFbpRSIRJWXkJ+bdm/Fy6/AA==";
-
-	String apLogoutResponseSigAlg = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256;
-
-	String apLogoutResponseRelayState = "8f63887a-ec7e-4149-b6a0-dd730017f315";
-
-	String apLogoutResponseSignature = "h2fDqSIBfmnkRHKDMY4IxkCXcI0w98ydNsnPmv1b7GTZCWLbJ+oxaP2yZNPw7wOWXTv86cTPwKLjx5halKy5C+hhWnT0haKhuMcUvHlsgAMBbJKLV+1afzL4O77cvAQJmMNRK7ugXGNV5PTEnd1U4voy134OgdD5XycYiFVRZOwP5H84eJ9xxlvqQwqDvZTcgiF/ZS4ioZgzgnIFcbagZQ12LWNh26OMaUpIW04kCeO6t2dUsxOL6nZWvNrX/Zx1sORIpu4doDUa1RYC8YnjZeQEzDqUVC/dBO/mbVJ/hbF9tD0jBUx7YIgoXpqsWK4TcCsvmlmhrJXvGxDyoAWu2Q==";
-
-	String rpLogoutRequest = "nZFBa4MwGIb/iuQeY6NlGtQykIHgdui6HXaLmrqAJlm+OLp/v0wrlB122CXkI3mfNw/JD5dpDD6FBalVgXZhhAKhOt1LNRTo5fSAU3Qoc+DTSA1r9KBndxQfswAX+KQCth4VaLaKaQ4SmOKTAOY69nz/2DAaRsxY7XSnRxRUPigVd0vbu3MGGCHchOLCJzOKUNuBjEsLWcDErmUoqKsCNcc+yc5tsudYpPwOJzHvcJv6pfdjEtNzl7XU3wWYRa3AceUKRCO6w1GM6f5EY0Ypo1lIk+gNBa+bt38kulqyJWxv7f6W4wDC/gih0hoslJPuC8s+J7e4Df7k43X1L/jsdxt0xZTX8dfHlN8=";
-
-	String rpLogoutRequestId = "LRd49fb45a-e8a7-43ac-b8ac-d8a7432fc9b2";
-
-	String rpLogoutRequestRelayState = "8f63887a-ec7e-4149-b6a0-dd730017f315";
-
-	String rpLogoutRequestSignature = "h2fDqSIBfmnkRHKDMY4IxkCXcI0w98ydNsnPmv1b7GTZCWLbJ+oxaP2yZNPw7wOWXTv86cTPwKLjx5halKy5C+hhWnT0haKhuMcUvHlsgAMBbJKLV+1afzL4O77cvAQJmMNRK7ugXGNV5PTEnd1U4voy134OgdD5XycYiFVRZOwP5H84eJ9xxlvqQwqDvZTcgiF/ZS4ioZgzgnIFcbagZQ12LWNh26OMaUpIW04kCeO6t2dUsxOL6nZWvNrX/Zx1sORIpu4doDUa1RYC8YnjZeQEzDqUVC/dBO/mbVJ/hbF9tD0jBUx7YIgoXpqsWK4TcCsvmlmhrJXvGxDyoAWu2Q==";
-
-	private MockHttpServletRequest request;
-
-	private MockHttpServletResponse response;
-
-	private MockFilterChain filterChain;
-
-	@Before
-	public void setup() {
-		this.request = new MockHttpServletRequest("POST", "");
-		this.request.setServletPath("/login/saml2/sso/test-rp");
-		this.response = new MockHttpServletResponse();
-		this.filterChain = new MockFilterChain();
-	}
-
-	@After
-	public void cleanup() {
-		if (this.context != null) {
-			this.context.close();
-		}
-		reset(Saml2LogoutDefaultsConfig.mockLogoutHandler);
-	}
-
-	@Test
-	public void saml2LogoutWhenDefaultsThenLogsOutAndSendsLogoutRequest() throws Exception {
-		this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
-		MvcResult result = this.mvc.perform(post("/logout").with(authentication(this.user)).with(csrf()))
-				.andExpect(status().isFound()).andReturn();
-		String location = result.getResponse().getHeader("Location");
-		assertThat(location).startsWith("https://ap.example.org/logout/saml2/request");
-		verify(Saml2LogoutDefaultsConfig.mockLogoutHandler).logout(any(), any(), any());
-	}
-
-	@Test
-	public void saml2LogoutWhenUnauthenticatedThenEntryPoint() throws Exception {
-		this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
-		this.mvc.perform(post("/logout").with(csrf())).andExpect(status().isFound())
-				.andExpect(redirectedUrl("/login?logout"));
-	}
-
-	@Test
-	public void saml2LogoutWhenMissingCsrfThen403() throws Exception {
-		this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
-		this.mvc.perform(post("/logout").with(authentication(this.user))).andExpect(status().isForbidden());
-		verifyNoInteractions(Saml2LogoutDefaultsConfig.mockLogoutHandler);
-	}
-
-	@Test
-	public void saml2LogoutWhenGetThenDefaultLogoutPage() throws Exception {
-		this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
-		MvcResult result = this.mvc.perform(get("/logout").with(authentication(this.user)).with(csrf()))
-				.andExpect(status().isOk()).andReturn();
-		assertThat(result.getResponse().getContentAsString()).contains("Are you sure you want to log out?");
-		verifyNoInteractions(Saml2LogoutDefaultsConfig.mockLogoutHandler);
-	}
-
-	@Test
-	public void saml2LogoutWhenPutOrDeleteThen404() throws Exception {
-		this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
-		this.mvc.perform(put("/logout").with(authentication(this.user)).with(csrf())).andExpect(status().isNotFound());
-		this.mvc.perform(delete("/logout").with(authentication(this.user)).with(csrf()))
-				.andExpect(status().isNotFound());
-		verifyNoInteractions(Saml2LogoutDefaultsConfig.mockLogoutHandler);
-	}
-
-	@Test
-	public void saml2LogoutWhenNoRegistrationThenIllegalArgument() {
-		this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
-		Saml2Authentication authentication = new Saml2Authentication(
-				new DefaultSaml2AuthenticatedPrincipal("user", Collections.emptyMap()), "response",
-				AuthorityUtils.createAuthorityList("ROLE_USER"), "wrong");
-		assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(
-				() -> this.mvc.perform(post("/logout").with(authentication(authentication)).with(csrf())).andReturn());
-	}
-
-	@Test
-	public void saml2LogoutWhenCustomLogoutRequestResolverThenUses() throws Exception {
-		this.spring.register(Saml2LogoutComponentsConfig.class).autowire();
-		this.mvc.perform(post("/logout").with(authentication(this.user)).with(csrf()));
-		verify(Saml2LogoutComponentsConfig.logoutRequestResolver).resolveLogoutRequest(any(), any());
-	}
-
-	@Test
-	public void saml2LogoutRequestWhenDefaultsThenLogsOutAndSendsLogoutResponse() throws Exception {
-		this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
-		MvcResult result = this.mvc
-				.perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest)
-						.param("RelayState", this.apLogoutRequestRelayState).param("SigAlg", this.apLogoutRequestSigAlg)
-						.param("Signature", this.apLogoutRequestSignature).with(authentication(this.user)))
-				.andExpect(status().isFound()).andReturn();
-		String location = result.getResponse().getHeader("Location");
-		assertThat(location).startsWith("https://ap.example.org/logout/saml2/response");
-		verify(Saml2LogoutDefaultsConfig.mockLogoutHandler).logout(any(), any(), any());
-	}
-
-	@Test
-	public void saml2LogoutRequestWhenNoRegistrationThenIllegalArgument() throws Exception {
-		this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
-		assertThatExceptionOfType(IllegalArgumentException.class)
-				.isThrownBy(() -> this.mvc.perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest)
-						.param("RelayState", this.apLogoutRequestRelayState).param("SigAlg", this.apLogoutRequestSigAlg)
-						.param("Signature", this.apLogoutRequestSignature)).andReturn());
-		verifyNoInteractions(Saml2LogoutDefaultsConfig.mockLogoutHandler);
-	}
-
-	@Test
-	public void saml2LogoutRequestWhenNoSamlRequestThen404() throws Exception {
-		this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
-		this.mvc.perform(get("/logout/saml2/slo").with(authentication(this.user))).andExpect(status().isNotFound());
-		verifyNoInteractions(Saml2LogoutDefaultsConfig.mockLogoutHandler);
-	}
-
-	@Test
-	public void saml2LogoutRequestWhenInvalidSamlRequestThenException() throws Exception {
-		this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
-		assertThatExceptionOfType(Saml2Exception.class)
-				.isThrownBy(() -> this.mvc
-						.perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest)
-								.param("RelayState", this.apLogoutRequestRelayState)
-								.param("SigAlg", this.apLogoutRequestSigAlg).with(authentication(this.user)))
-						.andReturn());
-		verifyNoInteractions(Saml2LogoutDefaultsConfig.mockLogoutHandler);
-	}
-
-	@Test
-	public void saml2LogoutRequestWhenCustomLogoutRequestHandlerThenUses() throws Exception {
-		this.spring.register(Saml2LogoutComponentsConfig.class).autowire();
-		RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id");
-		LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
-		logoutRequest.setIssueInstant(Instant.now());
-		willAnswer((invocation) -> {
-			HttpServletRequest request = (HttpServletRequest) invocation.getArguments()[0];
-			request.setAttribute(LogoutRequest.class.getName(), logoutRequest);
-			return null;
-		}).given(Saml2LogoutComponentsConfig.logoutRequestHandler).logout(any(), any(), any());
-		Saml2LogoutResponseBuilder<?> partial = mock(Saml2LogoutResponseBuilder.class, RETURNS_SELF);
-		given(partial.logoutResponse())
-				.willReturn(Saml2LogoutResponse.withRelyingPartyRegistration(registration).build());
-		willReturn(partial).given(Saml2LogoutComponentsConfig.logoutResponseResolver).resolveLogoutResponse(any(),
-				any());
-		this.mvc.perform(get("/logout/saml2/slo").param("SAMLRequest", "samlRequest")).andReturn();
-		verify(Saml2LogoutComponentsConfig.logoutRequestHandler).logout(any(), any(), any());
-		verify(Saml2LogoutComponentsConfig.logoutResponseResolver).resolveLogoutResponse(any(), any());
-	}
-
-	@Test
-	public void saml2LogoutResponseWhenDefaultsThenRedirectsAndDoesNotLogout() throws Exception {
-		this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
-		RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id");
-		Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
-				.samlRequest(this.rpLogoutRequest).id(this.rpLogoutRequestId).relayState(this.rpLogoutRequestRelayState)
-				.parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)).build();
-		this.logoutRequestRepository.saveLogoutRequest(logoutRequest, this.request, this.response);
-		this.request.setParameter("RelayState", logoutRequest.getRelayState());
-		assertThat(this.logoutRequestRepository.loadLogoutRequest(this.request)).isNotNull();
-		this.mvc.perform(get("/logout/saml2/slo").session(((MockHttpSession) this.request.getSession()))
-				.param("SAMLResponse", this.apLogoutResponse).param("RelayState", this.apLogoutResponseRelayState)
-				.param("SigAlg", this.apLogoutResponseSigAlg).param("Signature", this.apLogoutResponseSignature))
-				.andExpect(status().isFound()).andExpect(redirectedUrl("/login?logout"));
-		verifyNoInteractions(Saml2LogoutDefaultsConfig.mockLogoutHandler);
-		assertThat(this.logoutRequestRepository.loadLogoutRequest(this.request)).isNull();
-	}
-
-	@Test
-	public void saml2LogoutResponseWhenNoMatchingLogoutRequestThenSaml2Exception() throws Exception {
-		this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
-		assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> this.mvc.perform(get("/logout/saml2/slo")
-				.param("SAMLResponse", this.apLogoutResponse).param("RelayState", this.apLogoutResponseRelayState)
-				.param("SigAlg", this.apLogoutResponseSigAlg).param("Signature", this.apLogoutResponseSignature)));
-		verifyNoInteractions(Saml2LogoutDefaultsConfig.mockLogoutHandler);
-	}
-
-	@Test
-	public void saml2LogoutResponseWhenNoSamlResponseThenEntryPoint() throws Exception {
-		this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
-		this.mvc.perform(get("/logout/saml2/slo")).andExpect(status().isFound())
-				.andExpect(redirectedUrl("http://localhost/login"));
-		verifyNoInteractions(Saml2LogoutDefaultsConfig.mockLogoutHandler);
-	}
-
-	@Test
-	public void saml2LogoutResponseWhenInvalidSamlResponseThenException() {
-		this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
-		RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id");
-		Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
-				.samlRequest(this.rpLogoutRequest).id(this.rpLogoutRequestId).relayState(this.rpLogoutRequestRelayState)
-				.parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)).build();
-		this.logoutRequestRepository.saveLogoutRequest(logoutRequest, this.request, this.response);
-		assertThatExceptionOfType(Saml2Exception.class).isThrownBy(
-				() -> this.mvc.perform(get("/logout/saml2/slo").session((MockHttpSession) this.request.getSession())
-						.param("SAMLResponse", this.apLogoutRequest).param("RelayState", this.apLogoutRequestRelayState)
-						.param("SigAlg", this.apLogoutRequestSigAlg)).andReturn());
-		verifyNoInteractions(Saml2LogoutDefaultsConfig.mockLogoutHandler);
-	}
-
-	@Test
-	public void saml2LogoutResponseWhenCustomLogoutResponseHandlerThenUses() throws Exception {
-		this.spring.register(Saml2LogoutComponentsConfig.class).autowire();
-		this.mvc.perform(get("/logout/saml2/slo").param("SAMLResponse", "samlResponse")).andReturn();
-		verify(Saml2LogoutComponentsConfig.logoutResponseHandler).logout(any(), any(), any());
-	}
-
-	@EnableWebSecurity
-	@Import(Saml2LoginConfigBeans.class)
-	static class Saml2LogoutDefaultsConfig {
-
-		static final LogoutHandler mockLogoutHandler = mock(LogoutHandler.class);
-
-		@Bean
-		SecurityFilterChain web(HttpSecurity http) throws Exception {
-			http.authorizeRequests((authorize) -> authorize.anyRequest().authenticated()).saml2Login(withDefaults())
-					.saml2Logout((logout) -> logout.addLogoutHandler(mockLogoutHandler));
-			return http.build();
-		}
-
-	}
-
-	@EnableWebSecurity
-	@Import(Saml2LoginConfigBeans.class)
-	static class Saml2LogoutComponentsConfig {
-
-		static final Saml2LogoutRequestRepository logoutRequestRepository = mock(Saml2LogoutRequestRepository.class);
-		static final LogoutHandler logoutRequestHandler = mock(LogoutHandler.class);
-		static final Saml2LogoutRequestResolver logoutRequestResolver = mock(Saml2LogoutRequestResolver.class);
-		static final LogoutHandler logoutResponseHandler = mock(LogoutHandler.class);
-		static final Saml2LogoutResponseResolver logoutResponseResolver = mock(Saml2LogoutResponseResolver.class);
-
-		@Bean
-		SecurityFilterChain web(HttpSecurity http) throws Exception {
-			http.authorizeRequests((authorize) -> authorize.anyRequest().authenticated()).saml2Login(withDefaults())
-					.saml2Logout((logout) -> logout
-							.logoutRequest((request) -> request.logoutRequestRepository(logoutRequestRepository)
-									.logoutRequestHandler(logoutRequestHandler)
-									.logoutRequestResolver(logoutRequestResolver))
-							.logoutResponse((response) -> response.logoutResponseHandler(logoutResponseHandler)
-									.logoutResponseResolver(logoutResponseResolver)));
-			return http.build();
-		}
-
-	}
-
-	static class Saml2LoginConfigBeans {
-
-		@Bean
-		RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
-			Saml2X509Credential signing = TestSaml2X509Credentials.assertingPartySigningCredential();
-			Saml2X509Credential verification = TestSaml2X509Credentials.relyingPartyVerifyingCredential();
-			RelyingPartyRegistration.Builder withCreds = TestRelyingPartyRegistrations.noCredentials()
-					.signingX509Credentials(credential(signing))
-					.assertingPartyDetails((party) -> party.verificationX509Credentials(credential(verification)));
-			RelyingPartyRegistration registration = withCreds.build();
-			RelyingPartyRegistration ap = withCreds.registrationId("ap").entityId("ap-entity-id")
-					.assertingPartyDetails((party) -> party
-							.singleLogoutServiceLocation("https://rp.example.org/logout/saml2/request")
-							.singleLogoutServiceResponseLocation("https://rp.example.org/logout/saml2/response"))
-					.build();
-
-			return new InMemoryRelyingPartyRegistrationRepository(ap, registration);
-		}
-
-		private Consumer<Collection<Saml2X509Credential>> credential(Saml2X509Credential credential) {
-			return (credentials) -> credentials.add(credential);
-		}
-
-	}
-
-}

+ 119 - 136
docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc

@@ -1074,7 +1074,9 @@ To use Spring Security's SAML 2.0 Single Logout feature, you will need the follo
 * Second, the asserting party should be configured to sign and POST `saml2:LogoutRequest` s and `saml2:LogoutResponse` s your application's `/logout/saml2/slo` endpoint
 * Second, the asserting party should be configured to sign and POST `saml2:LogoutRequest` s and `saml2:LogoutResponse` s your application's `/logout/saml2/slo` endpoint
 * Third, your application must have a PKCS#8 private key and X.509 certificate for signing `saml2:LogoutRequest` s and `saml2:LogoutResponse` s
 * Third, your application must have a PKCS#8 private key and X.509 certificate for signing `saml2:LogoutRequest` s and `saml2:LogoutResponse` s
 
 
-You can begin from the initial minimal example and add the following configuration:
+==== RP-Initiated Single Logout
+
+Given those, then for RP-initiated Single Logout, you can begin from the initial minimal example and add the following configuration:
 
 
 [source,java]
 [source,java]
 ----
 ----
@@ -1103,15 +1105,28 @@ SecurityFilterChain web(HttpSecurity http, RelyingPartyRegistrationRepository re
             .anyRequest().authenticated()
             .anyRequest().authenticated()
         )
         )
         .saml2Login(withDefaults())
         .saml2Login(withDefaults())
-        .saml2Logout(withDefaults()); <2>
+        .logout((logout) -> logout
+                .logoutUrl("/saml2/logout")
+                .logoutSuccessHandler(successHandler))
+        .addFilterBefore(new Saml2LogoutResponseFilter(logoutHandler), CsrfFilter.class);
 
 
     return http.build();
     return http.build();
 }
 }
+
+private LogoutSuccessHandler logoutRequestSuccessHandler(RelyingPartyRegistrationResolver registrationResolver) { <2>
+    OpenSaml4LogoutRequestResolver logoutRequestResolver = new OpenSaml4LogoutRequestResolver(registrationResolver);
+    return new Saml2LogoutRequestSuccessHandler(logoutRequestResolver);
+}
+
+private LogoutHandler logoutHandler(RelyingPartyRegistrationResolver registrationResolver) { <3>
+    return new OpenSamlLogoutResponseHandler(relyingPartyRegistrationResolver);
+}
 ----
 ----
 <1> - First, add your signing key to the `RelyingPartyRegistration` instance or to <<servlet-saml2login-rpr-duplicated,multiple instances>>
 <1> - First, add your signing key to the `RelyingPartyRegistration` instance or to <<servlet-saml2login-rpr-duplicated,multiple instances>>
-<2> - Second, indicate that your application wants to use SAML SLO to logout the end user
+<2> - Second, supply a `LogoutSuccessHandler` for initiating Single Logout, sending a `saml2:LogoutRequest` to the asserting party
+<3> - Third, supply the `LogoutHandler` s needed to handle the `saml2:LogoutResponse` s sent from the asserting party.
 
 
-==== Runtime Expectations
+==== Runtime Expectations for RP-Initiated
 
 
 Given the above configuration any logged in user can send a `POST /logout` to your application to perform RP-initiated SLO.
 Given the above configuration any logged in user can send a `POST /logout` to your application to perform RP-initiated SLO.
 Your application will then do the following:
 Your application will then do the following:
@@ -1122,6 +1137,63 @@ Your application will then do the following:
 4. Deserialize, verify, and process the `<saml2:LogoutResponse>` sent by the asserting party
 4. Deserialize, verify, and process the `<saml2:LogoutResponse>` sent by the asserting party
 5. Redirect to any configured successful logout endpoint
 5. Redirect to any configured successful logout endpoint
 
 
+[TIP]
+If your asserting party does not send `<saml2:LogoutResponse>` s when logout is complete, the asserting party can still send a `POST /saml2/logout` and then there is no need to configure the `Saml2LogoutResponseHandler`.
+
+==== AP-Initiated Single Logout
+
+Instead of RP-initiated Single Logout, you can again begin from the initial minimal example and add the following configuration to achieve AP-initiated Single Logout:
+
+[source,java]
+----
+@Value("${private.key}") RSAPrivateKey key;
+@Value("${public.certificate}") X509Certificate certificate;
+
+@Bean
+RelyingPartyRegistrationRepository registrations() {
+    RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
+            .fromMetadataLocation("https://ap.example.org/metadata")
+            .registrationId("id")
+            .signingX509Credentials((signing) -> signing.add(Saml2X509Credential.signing(key, certificate))) <1>
+            .build();
+    return new InMemoryRelyingPartyRegistrationRepository(relyingPartyRegistration);
+}
+
+@Bean
+SecurityFilterChain web(HttpSecurity http, RelyingPartyRegistrationRepository registrations) throws Exception {
+	RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver(registrations);
+    LogoutHandler logoutRequestHandler = logoutRequestHandler(registrationResolver);
+    LogoutSuccessHandler logoutResponseSuccessHandler = logoutResponseSuccessHandler(registrationResolver);
+
+    http
+        .authorizeRequests((authorize) -> authorize
+            .anyRequest().authenticated()
+        )
+        .saml2Login(withDefaults())
+        .addFilterBefore(new Saml2LogoutRequestFilter(logoutResponseSuccessHandler, logoutRequestHandler), CsrfFilter.class);
+
+    return http.build();
+}
+
+private LogoutHandler logoutHandler(RelyingPartyRegistrationResolver registrationResolver) { <2>
+    return new CompositeLogoutHandler(
+    		new OpenSamlLogoutRequestHandler(relyingPartyRegistrationResolver),
+            new SecurityContextLogoutHandler(),
+            new LogoutSuccessEventPublishingLogoutHandler());
+}
+
+private LogoutSuccessHandler logoutSuccessHandler(RelyingPartyRegistrationResolver registrationResolver) { <3>
+    OpenSaml4LogoutResponseResolver logoutResponseResolver = new OpenSaml4LogoutResponseResolver(registrationResolver);
+    return new Saml2LogoutResponseSuccessHandler(logoutResponseResolver);
+}
+----
+<1> - First, add your signing key to the `RelyingPartyRegistration` instance or to <<servlet-saml2login-rpr-duplicated,multiple instances>>
+<2> - Second, supply the `LogoutHandler` needed to handle the `saml2:LogoutRequest` s sent from the asserting party.
+<3> - Third, supply a `LogoutSuccessHandler` for completing Single Logout, sending a `saml2:LogoutResponse` to the asserting party
+
+==== Runtime Expectations for AP-Initiated
+
+Given the above configuration, an asserting party can send a `POST /logout/saml2` to your application that includes a `<saml2:LogoutRequest>`
 Also, your application can participate in an AP-initated logout when the asserting party sends a `<saml2:LogoutRequest>` to `/logout/saml2/slo`:
 Also, your application can participate in an AP-initated logout when the asserting party sends a `<saml2:LogoutRequest>` to `/logout/saml2/slo`:
 
 
 1. Use a `Saml2LogoutRequestHandler` to deserialize, verify, and process the `<saml2:LogoutRequest>` sent by the asserting party
 1. Use a `Saml2LogoutRequestHandler` to deserialize, verify, and process the `<saml2:LogoutRequest>` sent by the asserting party
@@ -1129,6 +1201,12 @@ Also, your application can participate in an AP-initated logout when the asserti
 3. Create, sign, and serialize a `<saml2:LogoutResponse>` based on the <<servlet-saml2login-relyingpartyregistration,`RelyingPartyRegistration`>> associated with the just logged-out user
 3. Create, sign, and serialize a `<saml2:LogoutResponse>` based on the <<servlet-saml2login-relyingpartyregistration,`RelyingPartyRegistration`>> associated with the just logged-out user
 4. Send a redirect or post to the asserting party based on the <<servlet-saml2login-relyingpartyregistration,`RelyingPartyRegistration`>>
 4. Send a redirect or post to the asserting party based on the <<servlet-saml2login-relyingpartyregistration,`RelyingPartyRegistration`>>
 
 
+[TIP]
+If your asserting party does not expect you do send a `<saml2:LogoutResponse>` s when logout is complete, you may not need to configure a `LogoutSuccessHandler`
+
+[NOTE]
+In the event that you need to support both logout flows, you can combine the above to configurations.
+
 === Configuring Logout Endpoints
 === Configuring Logout Endpoints
 
 
 There are three default endpoints that Spring Security's SAML 2.0 Single Logout support exposes:
 There are three default endpoints that Spring Security's SAML 2.0 Single Logout support exposes:
@@ -1145,12 +1223,11 @@ To reduce changes in configuration for the asserting party, you can configure th
 
 
 [source,java]
 [source,java]
 ----
 ----
-RequestMatcher slo = new AntPathRequestMatcher("/SLOService.saml2", "GET");
+Saml2LogoutResponseFilter filter = new Saml2LogoutResponseFilter(logoutHandler);
+filter.setLogoutRequestMatcher(new AntPathRequestMatcher("/SLOService.saml2", "GET"));
 http
 http
-    .saml2Logout((saml2) -> saml2
-        .logoutRequest((request) -> request.logoutRequestMatcher(slo))
-        .logoutResponse((response) -> response.logoutRequestMatcher(slo))
-    );
+    // ...
+    .addFilterBefore(filter, CsrfFilter.class);
 ----
 ----
 
 
 === Customizing `<saml2:LogoutRequest>` Resolution
 === Customizing `<saml2:LogoutRequest>` Resolution
@@ -1168,40 +1245,22 @@ To add other values, you can use delegation, like so:
 
 
 [source,java]
 [source,java]
 ----
 ----
-@Component
-public class MyOpenSamlLogoutRequestResolver implements Saml2LogoutRequestResolver {
-	private final OpenSaml3LogoutRequestResolver logoutRequestResolver;
-
-	public MyOpenSamlLogoutRequestResolver(RelyingPartyRegistrationRepository registrations) {
-		RelyingPartyRegistrationResolver relyingPartyRegistrationResolver =
-                new DefaultRelyingPartyRegistrationResolver(registrations);
-		this.logoutRequestResolver = new OpenSaml3LogoutRequestResolver(relyingPartyRegistrationResolver);
-	}
-
-	@Override
-    public OpenSamlLogoutRequestBuilder resolveLogoutRequest(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
-		String name = ((Saml2AuthenticatedPrincipal) authentication.getPrincipal()).getFirstAttribute("CustomAttribute");
-		String format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient";
-	    return logoutRequestResolver.resolveLogoutRequest(request, authentication) <1>
-                .name(name) <2>
-                .logoutRequest((logoutRequest) -> logoutRequest.getNameID().setFormat(format));
-    }
-}
+OpenSamlLogoutRequestResolver delegate = new OpenSamlLogoutRequestResolver(registrationResolver);
+return (request, response, authentication) -> {
+	OpenSamlLogoutRequestBuilder builder = delegate.resolveLogoutRequest(request, response, authentication); <1>
+	builder.name(((Saml2AuthenticatedPrincipal) authentication.getPrincipal()).getFirstAttribute("CustomAttribute")); <2>
+	builder.logoutRequest((logoutRequest) -> logoutRequest.setIssueInstant(DateTime.now()));
+	return builder.logoutRequest(); <3>
+};
 ----
 ----
 <1> - Spring Security applies default values to a `<saml2:LogoutRequest>`
 <1> - Spring Security applies default values to a `<saml2:LogoutRequest>`
 <2> - Your application specifies customizations
 <2> - Your application specifies customizations
+<3> - You complete the invocation by calling `request()`
 
 
-Then, you can supply your custom `Saml2LogoutRequestResolver` in the DSL as follows:
-
-[source,java]
-----
-http
-    .saml2Logout((saml2) -> saml2
-        .logoutRequest((request) -> request
-            .logoutRequestResolver(myOpenSamlLogoutRequestResolver)
-        )
-    );
-----
+[NOTE]
+Support for OpenSAML 4 is coming.
+In anticipation of that, `OpenSamlLogoutRequestResolver` does not add an `IssueInstant`.
+Once OpenSAML 4 support is added, the default will be able to appropriate negotiate that datatype change, meaning you will no longer have to set it.
 
 
 === Customizing `<saml2:LogoutResponse>` Resolution
 === Customizing `<saml2:LogoutResponse>` Resolution
 
 
@@ -1218,42 +1277,24 @@ To add other values, you can use delegation, like so:
 
 
 [source,java]
 [source,java]
 ----
 ----
-@Component
-public class MyOpenSamlLogoutResponseResolver implements Saml2LogoutRequestResolver {
-	private final OpenSaml3LogoutResponseResolver logoutRequestResolver;
-
-	public MyOpenSamlLogoutResponseResolver(RelyingPartyRegistrationRepository registrations) {
-		RelyingPartyRegistrationResolver relyingPartyRegistrationResolver =
-                new DefaultRelyingPartyRegistrationResolver(registrations);
-		this.logoutResponseResolver = new OpenSaml3LogoutResponseResolver(relyingPartyRegistrationResolver);
-	}
-
-	@Override
-    public OpenSamlLogoutResponseBuilder resolveLogoutResponse(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
-		String name = ((Saml2AuthenticatedPrincipal) authentication.getPrincipal()).getFirstAttribute("CustomAttribute");
-		String format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient";
-	    OpenSamlLogoutResponseBuilder builder =  logoutResponseResolver.resolveLogoutRequest(request, authentication); <1>
-        if (checkOtherPrevailingConditions()) {
-            builder.status(StatusCode.PARTIAL_LOGOUT); <2>
-        }
-        return builder;
+OpenSamlLogoutResponseResolver delegate = new OpenSamlLogoutResponseResolver(registrationResolver);
+return (request, response, authentication) -> {
+	OpenSamlLogoutResponseBuilder builder = delegate.resolveLogoutResponse(request, response, authentication); <1>
+    if (checkOtherPrevailingConditions()) {
+        builder.status(StatusCode.PARTIAL_LOGOUT); <2>
     }
     }
-}
+	builder.logoutResponse((logoutResponse) -> logoutResponse.setIssueInstant(DateTime.now()));
+	return builder.logoutResponse(); <3>
+};
 ----
 ----
 <1> - Spring Security applies default values to a `<saml2:LogoutResponse>`
 <1> - Spring Security applies default values to a `<saml2:LogoutResponse>`
 <2> - Your application specifies customizations
 <2> - Your application specifies customizations
+<3> - You complete the invocation by calling `response()`
 
 
-Then, you can supply your custom `Saml2LogoutResponseResolver` in the DSL as follows:
-
-[source,java]
-----
-http
-    .saml2Logout((saml2) -> saml2
-        .logoutRequest((request) -> request
-            .logoutRequestResolver(myOpenSamlLogoutRequestResolver)
-        )
-    );
-----
+[NOTE]
+Support for OpenSAML 4 is coming.
+In anticipation of that, `OpenSamlLogoutResponseResolver` does not add an `IssueInstant`.
+Once OpenSAML 4 support is added, the default will be able to appropriate negotiate that datatype change, meaning you will no longer have to set it.
 
 
 === Customizing `<saml2:LogoutRequest>` Validation
 === Customizing `<saml2:LogoutRequest>` Validation
 
 
@@ -1262,37 +1303,16 @@ At this point, the validation is minimal, so you may be able to first delegate t
 
 
 [source,java]
 [source,java]
 ----
 ----
-@Component
-public class MyOpenSamlLogoutRequestHandler implements LogoutHandler {
-	private final Saml2LogoutRequestHandler delegate;
-
-	public MyOpenSamlLogoutRequestHandler(RelyingPartyRegistrationRepository registrations) {
-		RelyingPartyRegistrationResolver relyingPartyRegistrationResolver =
-            new DefaultRelyingPartyRegistrationResolver(registrations);
-		this.delegate = new OpenSamlLogoutRequestHandler(relyingPartyRegistrationResolver);
-	}
-
-	@Override
-    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
+LogoutHandler logoutHandler(RelyingPartyRegistrationResolver registrationResolver) {
+	OpenSamlLogoutRequestHandler delegate = new OpenSamlLogoutRequestHandler(registrationResolver);
+	return (request, response, authentication) -> {
 		delegate.logout(request, response, authentication); // verify signature, issuer, destination, and principal name
 		delegate.logout(request, response, authentication); // verify signature, issuer, destination, and principal name
 		LogoutRequest logoutRequest = // ... parse using OpenSAML
 		LogoutRequest logoutRequest = // ... parse using OpenSAML
         // perform custom validation
         // perform custom validation
-    }
+	}
 }
 }
 ----
 ----
 
 
-Then, you can supply your custom `LogoutHandler` in the DSL as follows:
-
-[source,java]
-----
-http
-    .saml2Logout((saml2) -> saml2
-        .logoutRequest((request) -> request
-            .logoutRequestHandler(myOpenSamlLogoutRequestHandler)
-        )
-    );
-----
-
 === Customizing `<saml2:LogoutResponse>` Validation
 === Customizing `<saml2:LogoutResponse>` Validation
 
 
 To customize validation, you can implement your own `LogoutHandler`.
 To customize validation, you can implement your own `LogoutHandler`.
@@ -1300,49 +1320,12 @@ At this point, the validation is minimal, so you may be able to first delegate t
 
 
 [source,java]
 [source,java]
 ----
 ----
-@Component
-public class MyOpenSamlLogoutResponseHandler implements LogoutHandler {
-	private final Saml2LogoutResponseHandler delegate;
-
-	public MyOpenSamlLogoutResponseHandler(RelyingPartyRegistrationRepository registrations) {
-		RelyingPartyRegistrationResolver relyingPartyRegistrationResolver =
-            new DefaultRelyingPartyRegistrationResolver(registrations);
-		this.delegate = new OpenSamlLogoutResponseHandler(relyingPartyRegistrationResolver);
-	}
-
-	@Override
-    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
+LogoutHandler logoutHandler(RelyingPartyRegistrationResolver registrationResolver) {
+	OpenSamlLogoutResponseHandler delegate = new OpenSamlLogoutResponseHandler(registrationResolver);
+	return (request, response, authentication) -> {
 		delegate.logout(request, response, authentication); // verify signature, issuer, destination, and status
 		delegate.logout(request, response, authentication); // verify signature, issuer, destination, and status
 		LogoutResponse logoutResponse = // ... parse using OpenSAML
 		LogoutResponse logoutResponse = // ... parse using OpenSAML
         // perform custom validation
         // perform custom validation
-    }
+	}
 }
 }
 ----
 ----
-
-Then, you can supply your custom `LogoutHandler` in the DSL as follows:
-
-[source,java]
-----
-http
-    .saml2Logout((saml2) -> saml2
-        .logoutResponse((response) -> response
-            .logoutResponseHandler(myOpenSamlLogoutResponseHandler)
-        )
-    );
-----
-
-=== Customizing `<saml2:LogoutRequest>` storage
-
-When your application sends a `<saml2:LogoutRequest>`, the value is stored in the session so that the `RelayState` parameter and the `InResponseTo` attribute in the `<saml2:LogoutResponse>` can be verified.
-
-If you want to store logout requests in some place other than the session, you can supply your custom implementation in the DSL, like so:
-
-[source,java]
-----
-http
-    .saml2Logout((saml2) -> saml2
-        .logoutRequest((request) -> request
-            .logoutRequestRepository(myCustomLogoutRequestRepository)
-        )
-    );
-----