diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/logout/OidcClientInitiatedLogoutSuccessHandler.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/logout/OidcClientInitiatedLogoutSuccessHandler.java index 0ea7425a81..54ff6b7737 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/logout/OidcClientInitiatedLogoutSuccessHandler.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/logout/OidcClientInitiatedLogoutSuccessHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -18,6 +18,7 @@ package org.springframework.security.oauth2.client.oidc.web.logout; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.util.Collections; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -27,7 +28,9 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio 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.security.web.util.UrlUtils; import org.springframework.util.Assert; +import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; /** @@ -41,7 +44,7 @@ import org.springframework.web.util.UriComponentsBuilder; public final class OidcClientInitiatedLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { private final ClientRegistrationRepository clientRegistrationRepository; - private URI postLogoutRedirectUri; + private String postLogoutRedirectUri; public OidcClientInitiatedLogoutSuccessHandler(ClientRegistrationRepository clientRegistrationRepository) { Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null"); @@ -54,9 +57,14 @@ public final class OidcClientInitiatedLogoutSuccessHandler extends SimpleUrlLogo String targetUrl = null; URI endSessionEndpoint; if (authentication instanceof OAuth2AuthenticationToken && authentication.getPrincipal() instanceof OidcUser) { - endSessionEndpoint = this.endSessionEndpoint((OAuth2AuthenticationToken) authentication); + String registrationId = ((OAuth2AuthenticationToken) authentication).getAuthorizedClientRegistrationId(); + ClientRegistration clientRegistration = this.clientRegistrationRepository + .findByRegistrationId(registrationId); + endSessionEndpoint = this.endSessionEndpoint(clientRegistration); if (endSessionEndpoint != null) { - targetUrl = endpointUri(endSessionEndpoint, authentication); + String idToken = idToken(authentication); + URI postLogoutRedirectUri = postLogoutRedirectUri(request); + targetUrl = endpointUri(endSessionEndpoint, idToken, postLogoutRedirectUri); } } if (targetUrl == null) { @@ -66,13 +74,11 @@ public final class OidcClientInitiatedLogoutSuccessHandler extends SimpleUrlLogo return targetUrl; } - private URI endSessionEndpoint(OAuth2AuthenticationToken token) { - String registrationId = token.getAuthorizedClientRegistrationId(); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId); - + private URI endSessionEndpoint(ClientRegistration clientRegistration) { URI result = null; if (clientRegistration != null) { - Object endSessionEndpoint = clientRegistration.getProviderDetails().getConfigurationMetadata().get("end_session_endpoint"); + Object endSessionEndpoint = clientRegistration.getProviderDetails().getConfigurationMetadata() + .get("end_session_endpoint"); if (endSessionEndpoint != null) { result = URI.create(endSessionEndpoint.toString()); } @@ -81,25 +87,62 @@ public final class OidcClientInitiatedLogoutSuccessHandler extends SimpleUrlLogo return result; } - 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); + private String idToken(Authentication authentication) { + return ((OidcUser) authentication.getPrincipal()).getIdToken().getTokenValue(); + } + + private URI postLogoutRedirectUri(HttpServletRequest request) { + if (this.postLogoutRedirectUri == null) { + return null; } - return builder.encode(StandardCharsets.UTF_8).build().toUriString(); + UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request)) + .replacePath(request.getContextPath()) + .replaceQuery(null) + .fragment(null) + .build(); + return UriComponentsBuilder.fromUriString(this.postLogoutRedirectUri) + .buildAndExpand(Collections.singletonMap("baseUrl", uriComponents.toUriString())) + .toUri(); } - private String idToken(Authentication authentication) { - return ((OidcUser) authentication.getPrincipal()).getIdToken().getTokenValue(); + + private String endpointUri(URI endSessionEndpoint, String idToken, URI postLogoutRedirectUri) { + UriComponentsBuilder builder = UriComponentsBuilder.fromUri(endSessionEndpoint); + builder.queryParam("id_token_hint", idToken); + if (postLogoutRedirectUri != null) { + builder.queryParam("post_logout_redirect_uri", postLogoutRedirectUri); + } + return builder.encode(StandardCharsets.UTF_8).build().toUriString(); } /** * Set the post logout redirect uri to use * * @param postLogoutRedirectUri - A valid URL to which the OP should redirect after logging out the user + * @deprecated {@link #setPostLogoutRedirectUri(String)} */ + @Deprecated public void setPostLogoutRedirectUri(URI postLogoutRedirectUri) { + Assert.notNull(postLogoutRedirectUri, "postLogoutRedirectUri cannot be null"); + this.postLogoutRedirectUri = postLogoutRedirectUri.toASCIIString(); + } + + /** + * Set the post logout redirect uri template to use. Supports the {@code "{baseUrl}"} + * placeholder, for example: + * + *
+ * handler.setPostLogoutRedirectUriTemplate("{baseUrl}");
+ *
+ *
+ * will make so that {@code post_logout_redirect_uri} will be set to the base url for the client
+ * application.
+ *
+ * @param postLogoutRedirectUri - A template for creating the {@code post_logout_redirect_uri}
+ * query parameter
+ * @since 5.3
+ */
+ public void setPostLogoutRedirectUri(String postLogoutRedirectUri) {
Assert.notNull(postLogoutRedirectUri, "postLogoutRedirectUri cannot be null");
this.postLogoutRedirectUri = postLogoutRedirectUri;
}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandler.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandler.java
index b697bd0eaf..7668fd9e0c 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandler.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -18,9 +18,11 @@ package org.springframework.security.oauth2.client.oidc.web.server.logout;
import java.net.URI;
import java.nio.charset.StandardCharsets;
+import java.util.Collections;
import reactor.core.publisher.Mono;
+import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
@@ -32,6 +34,7 @@ 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.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
@@ -50,7 +53,7 @@ public class OidcClientInitiatedServerLogoutSuccessHandler
= new RedirectServerLogoutSuccessHandler();
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
- private URI postLogoutRedirectUri;
+ private String postLogoutRedirectUri;
/**
* Constructs an {@link OidcClientInitiatedServerLogoutSuccessHandler} with the provided parameters
@@ -74,28 +77,40 @@ public class OidcClientInitiatedServerLogoutSuccessHandler
.filter(OAuth2AuthenticationToken.class::isInstance)
.filter(token -> authentication.getPrincipal() instanceof OidcUser)
.map(OAuth2AuthenticationToken.class::cast)
- .flatMap(this::endSessionEndpoint)
- .map(endSessionEndpoint -> endpointUri(endSessionEndpoint, authentication))
+ .map(OAuth2AuthenticationToken::getAuthorizedClientRegistrationId)
+ .flatMap(this.clientRegistrationRepository::findByRegistrationId)
+ .flatMap(clientRegistration -> {
+ URI endSessionEndpoint = endSessionEndpoint(clientRegistration);
+ if (endSessionEndpoint == null) {
+ return Mono.empty();
+ }
+ String idToken = idToken(authentication);
+ URI postLogoutRedirectUri = postLogoutRedirectUri(exchange.getExchange().getRequest());
+ return Mono.just(endpointUri(endSessionEndpoint, idToken, postLogoutRedirectUri));
+ })
.switchIfEmpty(this.serverLogoutSuccessHandler
.onLogoutSuccess(exchange, authentication).then(Mono.empty()))
.flatMap(endpointUri -> this.redirectStrategy.sendRedirect(exchange.getExchange(), endpointUri));
}
- private Mono
+ * handler.setPostLogoutRedirectUriTemplate("{baseUrl}");
+ *
+ *
+ * will make so that {@code post_logout_redirect_uri} will be set to the base url for the client
+ * application.
+ *
+ * @param postLogoutRedirectUri - A template for creating the {@code post_logout_redirect_uri}
+ * query parameter
+ * @since 5.3
+ */
+ public void setPostLogoutRedirectUri(String 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/oidc/web/logout/OidcClientInitiatedLogoutSuccessHandlerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/logout/OidcClientInitiatedLogoutSuccessHandlerTests.java
index ff667fc04a..59a7c08150 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/logout/OidcClientInitiatedLogoutSuccessHandlerTests.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/logout/OidcClientInitiatedLogoutSuccessHandlerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -145,9 +145,35 @@ public class OidcClientInitiatedLogoutSuccessHandlerTests {
"post_logout_redirect_uri=https://postlogout?encodedparam%3Dvalue");
}
+ @Test
+ public void logoutWhenUsingPostLogoutRedirectUriTemplateThenBuildsItForRedirect()
+ throws IOException, ServletException {
+
+ OAuth2AuthenticationToken token = new OAuth2AuthenticationToken(
+ TestOidcUsers.create(),
+ AuthorityUtils.NO_AUTHORITIES,
+ this.registration.getRegistrationId());
+ this.handler.setPostLogoutRedirectUri("{baseUrl}");
+ this.request.setScheme("https");
+ this.request.setServerPort(443);
+ this.request.setServerName("rp.example.org");
+ this.request.setUserPrincipal(token);
+ this.handler.onLogoutSuccess(this.request, this.response, token);
+
+ assertThat(this.response.getRedirectedUrl()).isEqualTo("https://endpoint?" +
+ "id_token_hint=id-token&" +
+ "post_logout_redirect_uri=https://rp.example.org");
+ }
+
@Test
public void setPostLogoutRedirectUriWhenGivenNullThenThrowsException() {
- assertThatThrownBy(() -> this.handler.setPostLogoutRedirectUri(null))
+ assertThatThrownBy(() -> this.handler.setPostLogoutRedirectUri((URI) null))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ public void setPostLogoutRedirectUriTemplateWhenGivenNullThenThrowsException() {
+ assertThatThrownBy(() -> this.handler.setPostLogoutRedirectUri((String) null))
.isInstanceOf(IllegalArgumentException.class);
}
}
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java
index 199eb0c5ab..0dd7ad7209 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -16,8 +16,10 @@
package org.springframework.security.oauth2.client.oidc.web.server.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;
@@ -154,9 +156,37 @@ public class OidcClientInitiatedServerLogoutSuccessHandlerTests {
"post_logout_redirect_uri=https://postlogout?encodedparam%3Dvalue");
}
+ @Test
+ public void logoutWhenUsingPostLogoutRedirectUriTemplateThenBuildsItForRedirect()
+ throws IOException, ServletException {
+
+ OAuth2AuthenticationToken token = new OAuth2AuthenticationToken(
+ TestOidcUsers.create(),
+ AuthorityUtils.NO_AUTHORITIES,
+ this.registration.getRegistrationId());
+ when(this.exchange.getPrincipal()).thenReturn(Mono.just(token));
+ MockServerHttpRequest request = MockServerHttpRequest.get("https://rp.example.org/").build();
+ when(this.exchange.getRequest()).thenReturn(request);
+ WebFilterExchange f = new WebFilterExchange(exchange, this.chain);
+
+ this.handler.setPostLogoutRedirectUri("{baseUrl}");
+ this.handler.onLogoutSuccess(f, token).block();
+
+ assertThat(redirectedUrl(this.exchange))
+ .isEqualTo("https://endpoint?" +
+ "id_token_hint=id-token&" +
+ "post_logout_redirect_uri=https://rp.example.org");
+ }
+
@Test
public void setPostLogoutRedirectUriWhenGivenNullThenThrowsException() {
- assertThatThrownBy(() -> this.handler.setPostLogoutRedirectUri(null))
+ assertThatThrownBy(() -> this.handler.setPostLogoutRedirectUri((URI) null))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ public void setPostLogoutRedirectUriTemplateWhenGivenNullThenThrowsException() {
+ assertThatThrownBy(() -> this.handler.setPostLogoutRedirectUri((String) null))
.isInstanceOf(IllegalArgumentException.class);
}