diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcBackChannelLogoutHandler.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcBackChannelLogoutHandler.java index 0ee11281602..77bd077f1d0 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcBackChannelLogoutHandler.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcBackChannelLogoutHandler.java @@ -194,4 +194,13 @@ public void setSessionCookieName(String sessionCookieName) { this.sessionCookieName = sessionCookieName; } + /** + * Use this {@link RestOperations} to perform the per-session back-channel logout + * @param restOperations the {@link RestOperations} to use + */ + public void setRestOperations(RestOperations restOperations) { + Assert.notNull(restOperations, "restOperations cannot be null"); + this.restOperations = restOperations; + } + } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcLogoutConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcLogoutConfigurerTests.java index 4d1c54743f8..c3c17b826c7 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcLogoutConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcLogoutConfigurerTests.java @@ -90,6 +90,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.assertj.core.api.Assertions.assertThat; @@ -255,6 +256,23 @@ void logoutWhenDifferentCookieNameThenUses() throws Exception { this.mvc.perform(get("/token/logout").session(session)).andExpect(status().isUnauthorized()); } + @Test + void logoutWhenDifferentRestOperationsThenUses() throws Exception { + this.spring.register(OidcProviderConfig.class, CustomRestOperationsConfig.class).autowire(); + String registrationId = this.clientRegistration.getRegistrationId(); + MockHttpSession session = login(); + String logoutToken = this.mvc.perform(get("/token/logout").session(session)) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + this.mvc + .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) + .param("logout_token", logoutToken)) + .andExpect(status().isOk()); + this.mvc.perform(get("/token/logout").session(session)).andExpect(status().isUnauthorized()); + } + @Test void logoutWhenRemoteLogoutFailsThenReportsPartialLogout() throws Exception { this.spring.register(WebServerConfig.class, OidcProviderConfig.class, WithBrokenLogoutConfig.class).autowire(); @@ -485,6 +503,70 @@ void shutdown() throws IOException { } + @Configuration + @EnableWebSecurity + @Import(RegistrationConfig.class) + static class CustomRestOperationsConfig { + + private final MockWebServer server = new MockWebServer(); + + @Bean + @Order(1) + SecurityFilterChain filters(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) + .oauth2Login(Customizer.withDefaults()) + .oidcLogout((oidc) -> oidc + .backChannel(Customizer.withDefaults()) + ); + // @formatter:on + + return http.build(); + } + + @Bean + OidcSessionRegistry sessionRegistry() { + return new InMemoryOidcSessionRegistry(); + } + + @Bean + OidcBackChannelLogoutHandler oidcLogoutHandler(OidcSessionRegistry sessionRegistry) { + OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler(sessionRegistry); + + RestTemplate restTemplate = new RestTemplate(); + + restTemplate.getInterceptors().add((request, body, execution) -> { + request.getHeaders().add("X-EXTREMLY-IMPORTANT-CUSTOM-HEADER","IMPORTANT"); + return execution.execute(request, body); + }); + + logoutHandler.setRestOperations(restTemplate); + return logoutHandler; + } + + @Bean + MockWebServer web(ObjectProvider mvc) { + MockMvcDispatcher dispatcher = new MockMvcDispatcher(mvc); + dispatcher.setAssertion((rr) -> { + if(!rr.getRequestUrl().encodedPath().equals("/logout/connect/back-channel/registration-id")){ + return; + } + + String headerValue = rr.getHeaders().get("X-EXTREMLY-IMPORTANT-CUSTOM-HEADER"); + assertThat(headerValue).isEqualTo("IMPORTANT"); + }); + this.server.setDispatcher(dispatcher); + return this.server; + } + + @PreDestroy + void shutdown() throws IOException { + this.server.shutdown(); + } + + } + @Configuration @EnableWebSecurity @Import(RegistrationConfig.class) diff --git a/docs/modules/ROOT/pages/servlet/oauth2/login/logout.adoc b/docs/modules/ROOT/pages/servlet/oauth2/login/logout.adoc index bbd66f82a68..c21bffc6e01 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/login/logout.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/login/logout.adoc @@ -319,6 +319,43 @@ open fun oidcLogoutHandler(val sessionRegistry: OidcSessionRegistry): OidcBackCh ---- ====== + +=== Customizing the Rest Operations + +By default, a default `RestTemplate` is used to call the internal logout endpoint. You can set your own +if you e.g. need certain headers to be sent to your internal logout endpoint. + +You can configure the `RestOperations` in the DSL like so: + +[tabs] +====== +Java:: ++ +[source=java,role="primary"] +---- +@Bean +OidcBackChannelLogoutHandler oidcLogoutHandler(OidcSessionRegistry sessionRegistry) { + OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler(sessionRegistry); + RestTemplate customRestTemplate = new RestTemplate(); + logoutHandler.setRestOperations(customRestTemplate); + return logoutHandler; +} +---- + +Kotlin:: ++ +[source=kotlin,role="secondary"] +---- +@Bean +open fun oidcLogoutHandler(val sessionRegistry: OidcSessionRegistry): OidcBackChannelLogoutHandler { + val logoutHandler = OidcBackChannelLogoutHandler(sessionRegistry) + val restTemplate = RestTemplate() + logoutHandler.setRestOperations(restTemplate) + return logoutHandler +} +---- +====== + [[oidc-backchannel-logout-session-registry]] === Customizing the OIDC Provider Session Registry