Support Multiple ServerLogoutHandlers
This commit adds support to ServerHttpSecurity for registering multiple ServerLogoutHandlers. This is handy so that an application does not need to re-supply any handlers already configured by the DSL. Signed-off-by: blake_bauman <blake_bauman@apple.com>
This commit is contained in:
committed by
Josh Cummings
parent
686f8398dd
commit
a4f813ab29
+14
-1
@@ -3033,7 +3033,8 @@ public class ServerHttpSecurity {
|
||||
|
||||
/**
|
||||
* Configures the logout handler. Default is
|
||||
* {@code SecurityContextServerLogoutHandler}
|
||||
* {@code SecurityContextServerLogoutHandler}. This clears any previous handlers
|
||||
* configured.
|
||||
* @param logoutHandler
|
||||
* @return the {@link LogoutSpec} to configure
|
||||
*/
|
||||
@@ -3049,6 +3050,18 @@ public class ServerHttpSecurity {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows managing the list of {@link ServerLogoutHandler} instances.
|
||||
* @param handlersConsumer {@link Consumer} for managing the list of handlers.
|
||||
* @return the {@link LogoutSpec} to configure
|
||||
* @since 7.0
|
||||
*/
|
||||
public LogoutSpec logoutHandler(Consumer<List<ServerLogoutHandler>> handlersConsumer) {
|
||||
Assert.notNull(handlersConsumer, "consumer cannot be null");
|
||||
handlersConsumer.accept(this.logoutHandlers);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures what URL a POST to will trigger a log out.
|
||||
* @param logoutUrl the url to trigger a log out (i.e. "/signout" would mean a
|
||||
|
||||
+87
@@ -16,18 +16,27 @@
|
||||
|
||||
package org.springframework.security.config.web.server;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.htmlunit.server.WebTestClientHtmlUnitDriverBuilder;
|
||||
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
|
||||
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
|
||||
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
@@ -210,6 +219,84 @@ public class LogoutSpecTests {
|
||||
FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class).assertAt();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleLogoutHandlers() {
|
||||
InMemorySecurityContextRepository repository = new InMemorySecurityContextRepository();
|
||||
MultiValueMap<String, String> logoutData = new LinkedMultiValueMap<>();
|
||||
ServerLogoutHandler handler1 = (exchange, authentication) -> {
|
||||
logoutData.add("handler-header", "value1");
|
||||
return Mono.empty();
|
||||
};
|
||||
ServerLogoutHandler handler2 = (exchange, authentication) -> {
|
||||
logoutData.add("handler-header", "value2");
|
||||
return Mono.empty();
|
||||
};
|
||||
// @formatter:off
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
.securityContextRepository(repository)
|
||||
.authorizeExchange((authorize) -> authorize
|
||||
.anyExchange().authenticated())
|
||||
.formLogin(withDefaults())
|
||||
.logout((logoutSpec) -> logoutSpec.logoutHandler((handlers) -> {
|
||||
handlers.add(handler1);
|
||||
handlers.add(0, handler2);
|
||||
}))
|
||||
.build();
|
||||
WebTestClient webTestClient = WebTestClientBuilder
|
||||
.bindToWebFilters(securityWebFilter)
|
||||
.build();
|
||||
WebDriver driver = WebTestClientHtmlUnitDriverBuilder
|
||||
.webTestClientSetup(webTestClient)
|
||||
.build();
|
||||
// @formatter:on
|
||||
FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage
|
||||
.to(driver, FormLoginTests.DefaultLoginPage.class)
|
||||
.assertAt();
|
||||
// @formatter:off
|
||||
loginPage = loginPage.loginForm()
|
||||
.username("user")
|
||||
.password("invalid")
|
||||
.submit(FormLoginTests.DefaultLoginPage.class)
|
||||
.assertError();
|
||||
FormLoginTests.HomePage homePage = loginPage.loginForm()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.submit(FormLoginTests.HomePage.class);
|
||||
// @formatter:on
|
||||
homePage.assertAt();
|
||||
SecurityContext savedContext = repository.getSavedContext();
|
||||
assertThat(savedContext).isNotNull();
|
||||
assertThat(savedContext.getAuthentication()).isInstanceOf(UsernamePasswordAuthenticationToken.class);
|
||||
|
||||
loginPage = FormLoginTests.DefaultLogoutPage.to(driver).assertAt().logout();
|
||||
loginPage.assertAt().assertLogout();
|
||||
assertThat(logoutData).hasSize(1);
|
||||
assertThat(logoutData.get("handler-header")).containsExactly("value2", "value1");
|
||||
savedContext = repository.getSavedContext();
|
||||
assertThat(savedContext).isNull();
|
||||
}
|
||||
|
||||
private static class InMemorySecurityContextRepository implements ServerSecurityContextRepository {
|
||||
|
||||
@Nullable private SecurityContext savedContext;
|
||||
|
||||
@Override
|
||||
public Mono<Void> save(ServerWebExchange exchange, SecurityContext context) {
|
||||
this.savedContext = context;
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SecurityContext> load(ServerWebExchange exchange) {
|
||||
return Mono.justOrEmpty(this.savedContext);
|
||||
}
|
||||
|
||||
@Nullable private SecurityContext getSavedContext() {
|
||||
return this.savedContext;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
public static class HomeController {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user