From 0273b843f43b0b0f9fd27833e570b3cfea443d0a Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 16 May 2018 14:12:28 -0600 Subject: [PATCH 1/4] Support for OIDC RP-Initiated Logout Fixes: gh-5350 --- .../client/OAuth2LoginConfigurerTests.java | 75 ++++++++- ...dcClientInitiatedLogoutSuccessHandler.java | 98 +++++++++++ .../TestOAuth2AuthenticationTokens.java | 37 +++++ ...entInitiatedLogoutSuccessHandlerTests.java | 153 ++++++++++++++++++ ...AuthenticationTargetUrlRequestHandler.java | 14 +- 5 files changed, 367 insertions(+), 10 deletions(-) create mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/oidc/logout/OidcClientInitiatedLogoutSuccessHandler.java create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/TestOAuth2AuthenticationTokens.java create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/oidc/logout/OidcClientInitiatedLogoutSuccessHandlerTests.java diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java index 4f052871c2a..6630fd3a03d 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java @@ -15,10 +15,20 @@ */ package org.springframework.security.config.annotation.web.configurers.oauth2.client; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.apache.http.HttpHeaders; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; + import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; @@ -35,17 +45,22 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; +import org.springframework.security.config.test.SpringTestRule; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.context.SecurityContextImpl; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; +import org.springframework.security.oauth2.client.web.oidc.logout.OidcClientInitiatedLogoutSuccessHandler; import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; @@ -61,6 +76,7 @@ import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; +import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; @@ -71,21 +87,18 @@ import org.springframework.security.web.context.HttpRequestResponseHolder; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.SecurityContextRepository; +import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +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.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; /** * Tests for {@link OAuth2LoginConfigurer}. @@ -115,6 +128,12 @@ public class OAuth2LoginConfigurerTests { @Autowired SecurityContextRepository securityContextRepository; + @Rule + public final SpringTestRule spring = new SpringTestRule(); + + @Autowired(required = false) + MockMvc mvc; + private MockHttpServletRequest request; private MockHttpServletResponse response; private MockFilterChain filterChain; @@ -455,6 +474,21 @@ public void oidcLoginCustomWithNoUniqueJwtDecoderFactory() { "available: expected single matching bean but found 2: jwtDecoderFactory1,jwtDecoderFactory2"); } + @Test + public void logoutWhenUsingOidcLogoutHandlerThenRedirects() throws Exception { + this.spring.register(OAuth2LoginConfigWithOidcLogoutSuccessHandler.class).autowire(); + + OAuth2AuthenticationToken token = new OAuth2AuthenticationToken( + TestOidcUsers.create(), + AuthorityUtils.NO_AUTHORITIES, + "registration-id"); + + this.mvc.perform(post("/logout") + .with(authentication(token)) + .with(csrf())) + .andExpect(redirectedUrl("http://logout?id_token_hint=id-token")); + } + private void loadConfig(Class... configs) { AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(); applicationContext.register(configs); @@ -591,6 +625,31 @@ protected void configure(HttpSecurity http) throws Exception { } } + @EnableWebSecurity + static class OAuth2LoginConfigWithOidcLogoutSuccessHandler extends CommonWebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .logout() + .logoutSuccessHandler(oidcLogoutSuccessHandler()); + super.configure(http); + } + + @Bean + OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler() { + return new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository()); + } + + @Bean + ClientRegistrationRepository clientRegistrationRepository() { + Map providerMetadata = + Collections.singletonMap("end_session_endpoint", "http://logout"); + return new InMemoryClientRegistrationRepository( + TestClientRegistrations.clientRegistration() + .providerConfigurationMetadata(providerMetadata).build()); + } + } + private static abstract class CommonWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/oidc/logout/OidcClientInitiatedLogoutSuccessHandler.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/oidc/logout/OidcClientInitiatedLogoutSuccessHandler.java new file mode 100644 index 00000000000..c67882ed9f4 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/oidc/logout/OidcClientInitiatedLogoutSuccessHandler.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2019 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.oauth2.client.web.oidc.logout; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; +import org.springframework.util.Assert; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * A logout success handler for initiating OIDC logout through the user agent. + * + * @author Josh Cummings + * @since 5.2 + * @see RP-Initiated Logout + * @see org.springframework.security.web.authentication.logout.LogoutSuccessHandler + */ +public final class OidcClientInitiatedLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { + private final ClientRegistrationRepository clientRegistrationRepository; + + private URI postLogoutRedirectUri; + + public OidcClientInitiatedLogoutSuccessHandler(ClientRegistrationRepository clientRegistrationRepository) { + Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null"); + this.clientRegistrationRepository = clientRegistrationRepository; + } + + @Override + protected String determineTargetUrl(HttpServletRequest request, + HttpServletResponse response, Authentication authentication) { + + return Optional.of(authentication) + .filter(OAuth2AuthenticationToken.class::isInstance) + .filter(token -> authentication.getPrincipal() instanceof OidcUser) + .map(OAuth2AuthenticationToken.class::cast) + .flatMap(this::endSessionEndpoint) + .map(endSessionEndpoint -> endpointUri(endSessionEndpoint, authentication)) + .orElseGet(() -> super.determineTargetUrl(request, response)); + } + + private Optional endSessionEndpoint(OAuth2AuthenticationToken token) { + String registrationId = token.getAuthorizedClientRegistrationId(); + return Optional.of( + this.clientRegistrationRepository.findByRegistrationId(registrationId)) + .map(ClientRegistration::getProviderDetails) + .map(ClientRegistration.ProviderDetails::getConfigurationMetadata) + .map(configurationMetadata -> configurationMetadata.get("end_session_endpoint")) + .map(Object::toString) + .map(URI::create); + } + + private String endpointUri(URI endSessionEndpoint, Authentication authentication) { + UriComponentsBuilder builder = UriComponentsBuilder.fromUri(endSessionEndpoint); + builder.queryParam("id_token_hint", idToken(authentication)); + if (this.postLogoutRedirectUri != null) { + builder.queryParam("post_logout_redirect_uri", this.postLogoutRedirectUri); + } + return builder.encode(StandardCharsets.UTF_8).build().toUriString(); + } + + private String idToken(Authentication authentication) { + return ((OidcUser) authentication.getPrincipal()).getIdToken().getTokenValue(); + } + + /** + * Set the post logout redirect uri to use + * + * @param postLogoutRedirectUri - A valid URL to which the OP should redirect after logging out the user + */ + public void setPostLogoutRedirectUri(URI postLogoutRedirectUri) { + Assert.notNull(postLogoutRedirectUri, "postLogoutRedirectUri cannot be null"); + this.postLogoutRedirectUri = postLogoutRedirectUri; + } +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/TestOAuth2AuthenticationTokens.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/TestOAuth2AuthenticationTokens.java new file mode 100644 index 00000000000..df60ad55a3e --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/TestOAuth2AuthenticationTokens.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2019 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.oauth2.client.authentication; + +import java.util.Collection; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.oauth2.core.user.TestOAuth2Users; + +/** + * @author Josh Cummings + * @since 5.2 + */ +public class TestOAuth2AuthenticationTokens { + public static OAuth2AuthenticationToken authenticated(String... roles) { + OAuth2User principal = TestOAuth2Users.create(); + Collection authorities = AuthorityUtils.createAuthorityList(roles); + String registrationId = "registration-id"; + return new OAuth2AuthenticationToken(principal, authorities, registrationId); + } +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/oidc/logout/OidcClientInitiatedLogoutSuccessHandlerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/oidc/logout/OidcClientInitiatedLogoutSuccessHandlerTests.java new file mode 100644 index 00000000000..24c01ed530b --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/oidc/logout/OidcClientInitiatedLogoutSuccessHandlerTests.java @@ -0,0 +1,153 @@ +/* + * Copyright 2002-2019 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.oauth2.client.web.oidc.logout; + +import java.io.IOException; +import java.net.URI; +import java.util.Collections; +import javax.servlet.ServletException; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.TestClientRegistrations; +import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers; +import org.springframework.security.oauth2.core.user.TestOAuth2Users; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link OidcClientInitiatedLogoutSuccessHandler} + */ +@RunWith(MockitoJUnitRunner.class) +public class OidcClientInitiatedLogoutSuccessHandlerTests { + ClientRegistration registration = TestClientRegistrations + .clientRegistration() + .providerConfigurationMetadata( + Collections.singletonMap("end_session_endpoint", "http://endpoint")) + .build(); + ClientRegistrationRepository repository = new InMemoryClientRegistrationRepository(registration); + + MockHttpServletRequest request; + MockHttpServletResponse response; + + OidcClientInitiatedLogoutSuccessHandler handler; + + @Before + public void setup() { + this.request = new MockHttpServletRequest(); + this.response = new MockHttpServletResponse(); + this.handler = new OidcClientInitiatedLogoutSuccessHandler(this.repository); + } + + @Test + public void logoutWhenOidcRedirectUrlConfiguredThenRedirects() + throws IOException, ServletException { + OAuth2AuthenticationToken token = new OAuth2AuthenticationToken( + TestOidcUsers.create(), + AuthorityUtils.NO_AUTHORITIES, + this.registration.getRegistrationId()); + + this.request.setUserPrincipal(token); + this.handler.onLogoutSuccess(this.request, this.response, token); + + assertThat(this.response.getRedirectedUrl()).isEqualTo("http://endpoint?id_token_hint=id-token"); + } + + @Test + public void logoutWhenNotOAuth2AuthenticationThenDefaults() + throws IOException, ServletException { + Authentication token = mock(Authentication.class); + + this.request.setUserPrincipal(token); + this.handler.setDefaultTargetUrl("http://default"); + this.handler.onLogoutSuccess(this.request, this.response, token); + + assertThat(this.response.getRedirectedUrl()).isEqualTo("http://default"); + } + + @Test + public void logoutWhenNotOidcUserThenDefaults() + throws IOException, ServletException { + OAuth2AuthenticationToken token = new OAuth2AuthenticationToken( + TestOAuth2Users.create(), + AuthorityUtils.NO_AUTHORITIES, + this.registration.getRegistrationId()); + + this.request.setUserPrincipal(token); + this.handler.setDefaultTargetUrl("http://default"); + this.handler.onLogoutSuccess(this.request, this.response, token); + + assertThat(this.response.getRedirectedUrl()).isEqualTo("http://default"); + } + + @Test + public void logoutWhenClientRegistrationHasNoEndSessionEndpointThenDefaults() + throws Exception { + + ClientRegistration registration = TestClientRegistrations.clientRegistration().build(); + ClientRegistrationRepository repository = new InMemoryClientRegistrationRepository(registration); + OidcClientInitiatedLogoutSuccessHandler handler = new OidcClientInitiatedLogoutSuccessHandler(repository); + + OAuth2AuthenticationToken token = new OAuth2AuthenticationToken( + TestOidcUsers.create(), + AuthorityUtils.NO_AUTHORITIES, + registration.getRegistrationId()); + + this.request.setUserPrincipal(token); + handler.setDefaultTargetUrl("http://default"); + handler.onLogoutSuccess(this.request, this.response, token); + + assertThat(this.response.getRedirectedUrl()).isEqualTo("http://default"); + } + + @Test + public void logoutWhenUsingPostLogoutRedirectUriThenIncludesItInRedirect() + throws IOException, ServletException { + + OAuth2AuthenticationToken token = new OAuth2AuthenticationToken( + TestOidcUsers.create(), + AuthorityUtils.NO_AUTHORITIES, + this.registration.getRegistrationId()); + + this.handler.setPostLogoutRedirectUri(URI.create("http://postlogout?encodedparam=value")); + this.request.setUserPrincipal(token); + this.handler.onLogoutSuccess(this.request, this.response, token); + + assertThat(this.response.getRedirectedUrl()).isEqualTo("http://endpoint?" + + "id_token_hint=id-token&" + + "post_logout_redirect_uri=http://postlogout?encodedparam%3Dvalue"); + } + + @Test + public void setPostLogoutRedirectUriWhenGivenNullThenThrowsException() { + assertThatThrownBy(() -> this.handler.setPostLogoutRedirectUri(null)) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationTargetUrlRequestHandler.java b/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationTargetUrlRequestHandler.java index 39500c0b044..80aeeebe3a4 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationTargetUrlRequestHandler.java +++ b/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationTargetUrlRequestHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2019 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. @@ -80,7 +80,7 @@ protected AbstractAuthenticationTargetUrlRequestHandler() { */ protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - String targetUrl = determineTargetUrl(request, response); + String targetUrl = determineTargetUrl(request, response, authentication); if (response.isCommitted()) { logger.debug("Response has already been committed. Unable to redirect to " @@ -91,6 +91,16 @@ protected void handle(HttpServletRequest request, HttpServletResponse response, redirectStrategy.sendRedirect(request, response, targetUrl); } + /** + * Builds the target URL according to the logic defined in the main class Javadoc + * + * @since 5.2 + */ + protected String determineTargetUrl(HttpServletRequest request, + HttpServletResponse response, Authentication authentication) { + return determineTargetUrl(request, response); + } + /** * Builds the target URL according to the logic defined in the main class Javadoc. */ From 4f2bf54a692b7fb26b78ad245a603dc37c6701fd Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Tue, 5 Mar 2019 16:24:20 -0700 Subject: [PATCH 2/4] Reactive Oidc RP-Initiated Logout Issue: gh-5350 --- .../config/web/server/OAuth2LoginTests.java | 103 ++++++++++- ...ntInitiatedServerLogoutSuccessHandler.java | 126 ++++++++++++++ ...tiatedServerLogoutSuccessHandlerTests.java | 163 ++++++++++++++++++ 3 files changed, 385 insertions(+), 7 deletions(-) create mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/oidc/logout/OidcClientInitiatedServerLogoutSuccessHandler.java create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/oidc/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java index d4325704cba..81d94c34362 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java @@ -16,10 +16,18 @@ package org.springframework.security.config.web.server; +import java.time.Instant; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + import org.junit.Rule; import org.junit.Test; import org.openqa.selenium.WebDriver; +import reactor.core.publisher.Mono; + import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.ReactiveAuthenticationManager; @@ -27,15 +35,22 @@ import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; import org.springframework.security.config.test.SpringTestRule; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.htmlunit.server.WebTestClientHtmlUnitDriverBuilder; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken; import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeReactiveAuthenticationManager; +import org.springframework.security.oauth2.client.web.server.oidc.logout.OidcClientInitiatedServerLogoutSuccessHandler; import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.core.OAuth2AccessToken; @@ -60,21 +75,21 @@ import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; +import org.springframework.security.web.server.context.ServerSecurityContextRepository; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; -import reactor.core.publisher.Mono; - -import java.time.Instant; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import org.springframework.web.server.WebHandler; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * @author Rob Winch @@ -85,6 +100,8 @@ public class OAuth2LoginTests { @Rule public final SpringTestRule spring = new SpringTestRule(); + private WebTestClient client; + @Autowired private WebFilterChainProxy springSecurity; @@ -100,6 +117,14 @@ public class OAuth2LoginTests { .clientSecret("secret") .build(); + @Autowired + public void setApplicationContext(ApplicationContext context) { + if (context.getBeanNamesForType(WebHandler.class).length > 0) { + this.client = WebTestClient.bindToApplicationContext(context) + .build(); + } + } + @Test public void defaultLoginPageWithMultipleClientRegistrationsThenLinks() { this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class).autowire(); @@ -326,6 +351,60 @@ private ReactiveJwtDecoder getJwtDecoder() { } } + + @Test + public void logoutWhenUsingOidcLogoutHandlerThenRedirects() throws Exception { + this.spring.register(OAuth2LoginConfigWithOidcLogoutSuccessHandler.class).autowire(); + + OAuth2AuthenticationToken token = new OAuth2AuthenticationToken( + TestOidcUsers.create(), + AuthorityUtils.NO_AUTHORITIES, + getBean(ClientRegistration.class).getRegistrationId()); + + ServerSecurityContextRepository repository = getBean(ServerSecurityContextRepository.class); + when(repository.load(any())).thenReturn(authentication(token)); + + this.client.post().uri("/logout") + .exchange() + .expectHeader().valueEquals("Location", "http://logout?id_token_hint=id-token"); + } + + @EnableWebFlux + @EnableWebFluxSecurity + static class OAuth2LoginConfigWithOidcLogoutSuccessHandler { + private final ServerSecurityContextRepository repository = + mock(ServerSecurityContextRepository.class); + private final ClientRegistration withLogout = + TestClientRegistrations.clientRegistration() + .providerConfigurationMetadata(Collections.singletonMap( + "end_session_endpoint", "http://logout")).build(); + + @Bean + public SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { + + http + .csrf().disable() + .logout() + .logoutSuccessHandler( + new OidcClientInitiatedServerLogoutSuccessHandler( + new InMemoryReactiveClientRegistrationRepository(this.withLogout))) + .and() + .securityContextRepository(this.repository); + + return http.build(); + } + + @Bean + ServerSecurityContextRepository securityContextRepository() { + return this.repository; + } + + @Bean + ClientRegistration clientRegistration() { + return this.withLogout; + } + } + static class GitHubWebFilter implements WebFilter { @Override @@ -336,4 +415,14 @@ public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { return chain.filter(exchange); } } + + Mono authentication(Authentication authentication) { + SecurityContext context = new SecurityContextImpl(); + context.setAuthentication(authentication); + return Mono.just(context); + } + + T getBean(Class beanClass) { + return this.spring.getContext().getBean(beanClass); + } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/oidc/logout/OidcClientInitiatedServerLogoutSuccessHandler.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/oidc/logout/OidcClientInitiatedServerLogoutSuccessHandler.java new file mode 100644 index 00000000000..6f77c5f49f3 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/oidc/logout/OidcClientInitiatedServerLogoutSuccessHandler.java @@ -0,0 +1,126 @@ +/* + * Copyright 2002-2019 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.oauth2.client.web.server.oidc.logout; + +import java.net.URI; +import java.nio.charset.StandardCharsets; + +import reactor.core.publisher.Mono; + +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.web.server.DefaultServerRedirectStrategy; +import org.springframework.security.web.server.ServerRedirectStrategy; +import org.springframework.security.web.server.WebFilterExchange; +import org.springframework.security.web.server.authentication.logout.RedirectServerLogoutSuccessHandler; +import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler; +import org.springframework.util.Assert; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * A reactive logout success handler for initiating OIDC logout through the user agent. + * + * @author Josh Cummings + * @since 5.2 + * @see RP-Initiated Logout + * @see org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler + */ +public class OidcClientInitiatedServerLogoutSuccessHandler + implements ServerLogoutSuccessHandler { + + private final ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy(); + private final RedirectServerLogoutSuccessHandler serverLogoutSuccessHandler + = new RedirectServerLogoutSuccessHandler(); + private final ReactiveClientRegistrationRepository clientRegistrationRepository; + + private URI postLogoutRedirectUri; + + /** + * Constructs an {@link OidcClientInitiatedServerLogoutSuccessHandler} with the provided parameters + * + * @param clientRegistrationRepository The {@link ReactiveClientRegistrationRepository} to use to derive + * the end_session_endpoint value + */ + public OidcClientInitiatedServerLogoutSuccessHandler + (ReactiveClientRegistrationRepository clientRegistrationRepository) { + + Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null"); + this.clientRegistrationRepository = clientRegistrationRepository; + } + + /** + * {@inheritDoc} + */ + @Override + public Mono onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) { + return Mono.just(authentication) + .filter(OAuth2AuthenticationToken.class::isInstance) + .filter(token -> authentication.getPrincipal() instanceof OidcUser) + .map(OAuth2AuthenticationToken.class::cast) + .flatMap(this::endSessionEndpoint) + .map(endSessionEndpoint -> endpointUri(endSessionEndpoint, authentication)) + .switchIfEmpty(this.serverLogoutSuccessHandler + .onLogoutSuccess(exchange, authentication).then(Mono.empty())) + .flatMap(endpointUri -> this.redirectStrategy.sendRedirect(exchange.getExchange(), endpointUri)); + } + + private Mono endSessionEndpoint(OAuth2AuthenticationToken token) { + String registrationId = token.getAuthorizedClientRegistrationId(); + return this.clientRegistrationRepository.findByRegistrationId(registrationId) + .map(ClientRegistration::getProviderDetails) + .map(ClientRegistration.ProviderDetails::getConfigurationMetadata) + .flatMap(configurationMetadata -> Mono.justOrEmpty(configurationMetadata.get("end_session_endpoint"))) + .map(Object::toString) + .map(URI::create); + } + + private URI endpointUri(URI endSessionEndpoint, Authentication authentication) { + UriComponentsBuilder builder = UriComponentsBuilder.fromUri(endSessionEndpoint); + builder.queryParam("id_token_hint", idToken(authentication)); + if (this.postLogoutRedirectUri != null) { + builder.queryParam("post_logout_redirect_uri", this.postLogoutRedirectUri); + } + return builder.encode(StandardCharsets.UTF_8).build().toUri(); + } + + private String idToken(Authentication authentication) { + return ((OidcUser) authentication.getPrincipal()).getIdToken().getTokenValue(); + } + + /** + * Set the post logout redirect uri to use + * + * @param postLogoutRedirectUri - A valid URL to which the OP should redirect after logging out the user + */ + public void setPostLogoutRedirectUri(URI postLogoutRedirectUri) { + Assert.notNull(postLogoutRedirectUri, "postLogoutRedirectUri cannot be empty"); + this.postLogoutRedirectUri = postLogoutRedirectUri; + } + + /** + * The URL to redirect to after successfully logging out when not originally an OIDC login + * + * @param logoutSuccessUrl the url to redirect to. Default is "/login?logout". + */ + public void setLogoutSuccessUrl(URI logoutSuccessUrl) { + Assert.notNull(logoutSuccessUrl, "logoutSuccessUrl cannot be null"); + this.serverLogoutSuccessHandler.setLogoutSuccessUrl(logoutSuccessUrl); + } +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/oidc/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/oidc/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java new file mode 100644 index 00000000000..c891a158933 --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/oidc/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java @@ -0,0 +1,163 @@ +/* + * Copyright 2002-2019 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.oauth2.client.web.server.oidc.logout; + +import java.net.URI; +import java.util.Collections; + +import org.junit.Before; +import org.junit.Test; +import reactor.core.publisher.Mono; + +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.http.server.reactive.MockServerHttpResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.TestClientRegistrations; +import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers; +import org.springframework.security.oauth2.core.user.TestOAuth2Users; +import org.springframework.security.web.server.WebFilterExchange; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilterChain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class OidcClientInitiatedServerLogoutSuccessHandlerTests { + ClientRegistration registration = TestClientRegistrations + .clientRegistration() + .providerConfigurationMetadata( + Collections.singletonMap("end_session_endpoint", "http://endpoint")) + .build(); + ReactiveClientRegistrationRepository repository = new InMemoryReactiveClientRegistrationRepository(registration); + + ServerWebExchange exchange; + WebFilterChain chain; + + OidcClientInitiatedServerLogoutSuccessHandler handler; + + @Before + public void setup() { + this.exchange = mock(ServerWebExchange.class); + when(this.exchange.getResponse()).thenReturn(new MockServerHttpResponse()); + when(this.exchange.getRequest()).thenReturn(MockServerHttpRequest.get("/").build()); + this.chain = mock(WebFilterChain.class); + this.handler = new OidcClientInitiatedServerLogoutSuccessHandler(this.repository); + } + + @Test + public void logoutWhenOidcRedirectUrlConfiguredThenRedirects() { + OAuth2AuthenticationToken token = new OAuth2AuthenticationToken( + TestOidcUsers.create(), + AuthorityUtils.NO_AUTHORITIES, + this.registration.getRegistrationId()); + + when(this.exchange.getPrincipal()).thenReturn(Mono.just(token)); + WebFilterExchange f = new WebFilterExchange(exchange, this.chain); + this.handler.onLogoutSuccess(f, token).block(); + + assertThat(redirectedUrl(this.exchange)).isEqualTo("http://endpoint?id_token_hint=id-token"); + } + + @Test + public void logoutWhenNotOAuth2AuthenticationThenDefaults() { + Authentication token = mock(Authentication.class); + + when(this.exchange.getPrincipal()).thenReturn(Mono.just(token)); + WebFilterExchange f = new WebFilterExchange(exchange, this.chain); + + this.handler.setLogoutSuccessUrl(URI.create("http://default")); + this.handler.onLogoutSuccess(f, token).block(); + + assertThat(redirectedUrl(this.exchange)).isEqualTo("http://default"); + } + + @Test + public void logoutWhenNotOidcUserThenDefaults() { + OAuth2AuthenticationToken token = new OAuth2AuthenticationToken( + TestOAuth2Users.create(), + AuthorityUtils.NO_AUTHORITIES, + this.registration.getRegistrationId()); + + when(this.exchange.getPrincipal()).thenReturn(Mono.just(token)); + WebFilterExchange f = new WebFilterExchange(exchange, this.chain); + + this.handler.setLogoutSuccessUrl(URI.create("http://default")); + this.handler.onLogoutSuccess(f, token).block(); + + assertThat(redirectedUrl(this.exchange)).isEqualTo("http://default"); + } + + @Test + public void logoutWhenClientRegistrationHasNoEndSessionEndpointThenDefaults() { + + ClientRegistration registration = TestClientRegistrations.clientRegistration().build(); + ReactiveClientRegistrationRepository repository = + new InMemoryReactiveClientRegistrationRepository(registration); + OidcClientInitiatedServerLogoutSuccessHandler handler = + new OidcClientInitiatedServerLogoutSuccessHandler(repository); + + OAuth2AuthenticationToken token = new OAuth2AuthenticationToken( + TestOidcUsers.create(), + AuthorityUtils.NO_AUTHORITIES, + registration.getRegistrationId()); + + when(this.exchange.getPrincipal()).thenReturn(Mono.just(token)); + WebFilterExchange f = new WebFilterExchange(exchange, this.chain); + + handler.setLogoutSuccessUrl(URI.create("http://default")); + handler.onLogoutSuccess(f, token).block(); + + assertThat(redirectedUrl(this.exchange)).isEqualTo("http://default"); + } + + @Test + public void logoutWhenUsingPostLogoutRedirectUriThenIncludesItInRedirect() { + + OAuth2AuthenticationToken token = new OAuth2AuthenticationToken( + TestOidcUsers.create(), + AuthorityUtils.NO_AUTHORITIES, + this.registration.getRegistrationId()); + + when(this.exchange.getPrincipal()).thenReturn(Mono.just(token)); + WebFilterExchange f = new WebFilterExchange(exchange, this.chain); + + this.handler.setPostLogoutRedirectUri(URI.create("http://postlogout?encodedparam=value")); + this.handler.onLogoutSuccess(f, token).block(); + + assertThat(redirectedUrl(this.exchange)) + .isEqualTo("http://endpoint?" + + "id_token_hint=id-token&" + + "post_logout_redirect_uri=http://postlogout?encodedparam%3Dvalue"); + } + + @Test + public void setPostLogoutRedirectUriWhenGivenNullThenThrowsException() { + assertThatThrownBy(() -> this.handler.setPostLogoutRedirectUri(null)) + .isInstanceOf(IllegalArgumentException.class); + } + + private String redirectedUrl(ServerWebExchange exchange) { + return exchange.getResponse().getHeaders().getFirst("Location"); + } +} From 0232804c023598555823f9fe10f8b441cb0c8716 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Tue, 19 Mar 2019 08:15:08 -0600 Subject: [PATCH 3/4] Move OIDC Servlet Packaging --- .../configurers/oauth2/client/OAuth2LoginConfigurerTests.java | 2 +- .../web}/logout/OidcClientInitiatedLogoutSuccessHandler.java | 2 +- .../logout/OidcClientInitiatedLogoutSuccessHandlerTests.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/{web/oidc => oidc/web}/logout/OidcClientInitiatedLogoutSuccessHandler.java (98%) rename oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/{web/oidc => oidc/web}/logout/OidcClientInitiatedLogoutSuccessHandlerTests.java (98%) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java index 6630fd3a03d..da4112d7db8 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java @@ -55,7 +55,7 @@ import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; -import org.springframework.security.oauth2.client.web.oidc.logout.OidcClientInitiatedLogoutSuccessHandler; +import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler; import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/oidc/logout/OidcClientInitiatedLogoutSuccessHandler.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/logout/OidcClientInitiatedLogoutSuccessHandler.java similarity index 98% rename from oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/oidc/logout/OidcClientInitiatedLogoutSuccessHandler.java rename to oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/logout/OidcClientInitiatedLogoutSuccessHandler.java index c67882ed9f4..74fae426ea9 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/oidc/logout/OidcClientInitiatedLogoutSuccessHandler.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/logout/OidcClientInitiatedLogoutSuccessHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.security.oauth2.client.web.oidc.logout; +package org.springframework.security.oauth2.client.oidc.web.logout; import java.net.URI; import java.nio.charset.StandardCharsets; diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/oidc/logout/OidcClientInitiatedLogoutSuccessHandlerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/logout/OidcClientInitiatedLogoutSuccessHandlerTests.java similarity index 98% rename from oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/oidc/logout/OidcClientInitiatedLogoutSuccessHandlerTests.java rename to oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/logout/OidcClientInitiatedLogoutSuccessHandlerTests.java index 24c01ed530b..e580500f0f0 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/oidc/logout/OidcClientInitiatedLogoutSuccessHandlerTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/logout/OidcClientInitiatedLogoutSuccessHandlerTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.security.oauth2.client.web.oidc.logout; +package org.springframework.security.oauth2.client.oidc.web.logout; import java.io.IOException; import java.net.URI; From f1ceafc80eae5d6a010f5dc181480852450db45e Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Tue, 19 Mar 2019 08:15:36 -0600 Subject: [PATCH 4/4] Move OIDC Reactive Packaging --- .../security/config/web/server/OAuth2LoginTests.java | 2 +- .../OidcClientInitiatedServerLogoutSuccessHandler.java | 2 +- .../OidcClientInitiatedServerLogoutSuccessHandlerTests.java | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) rename oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/{web/server/oidc => oidc/web/server}/logout/OidcClientInitiatedServerLogoutSuccessHandler.java (98%) rename oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/{web/server/oidc => oidc/web/server}/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java (97%) diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java index 81d94c34362..bf14eda9f2e 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java @@ -46,7 +46,7 @@ import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeReactiveAuthenticationManager; -import org.springframework.security.oauth2.client.web.server.oidc.logout.OidcClientInitiatedServerLogoutSuccessHandler; +import org.springframework.security.oauth2.client.oidc.web.server.logout.OidcClientInitiatedServerLogoutSuccessHandler; import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/oidc/logout/OidcClientInitiatedServerLogoutSuccessHandler.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandler.java similarity index 98% rename from oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/oidc/logout/OidcClientInitiatedServerLogoutSuccessHandler.java rename to oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandler.java index 6f77c5f49f3..6bc467cd3fa 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/oidc/logout/OidcClientInitiatedServerLogoutSuccessHandler.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.security.oauth2.client.web.server.oidc.logout; +package org.springframework.security.oauth2.client.oidc.web.server.logout; import java.net.URI; import java.nio.charset.StandardCharsets; diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/oidc/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java similarity index 97% rename from oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/oidc/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java rename to oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java index c891a158933..a9bfcc4800f 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/oidc/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.security.oauth2.client.web.server.oidc.logout; +package org.springframework.security.oauth2.client.oidc.web.server.logout; import java.net.URI; import java.util.Collections; @@ -43,6 +43,9 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +/** + * Tests for {@link OidcClientInitiatedServerLogoutSuccessHandler} + */ public class OidcClientInitiatedServerLogoutSuccessHandlerTests { ClientRegistration registration = TestClientRegistrations .clientRegistration()