Skip to content

Commit cb06ab0

Browse files
Max BatischevMax Batischev
authored andcommitted
Add support customizing redirect URI
Closes gh-14778
1 parent 4c44de7 commit cb06ab0

File tree

2 files changed

+116
-17
lines changed

2 files changed

+116
-17
lines changed

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandler.java

Lines changed: 95 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323

2424
import reactor.core.publisher.Mono;
2525

26+
import org.springframework.core.convert.converter.Converter;
2627
import org.springframework.http.server.reactive.ServerHttpRequest;
2728
import org.springframework.security.core.Authentication;
2829
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
@@ -35,6 +36,7 @@
3536
import org.springframework.security.web.server.authentication.logout.RedirectServerLogoutSuccessHandler;
3637
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
3738
import org.springframework.util.Assert;
39+
import org.springframework.web.server.ServerWebExchange;
3840
import org.springframework.web.util.UriComponents;
3941
import org.springframework.web.util.UriComponentsBuilder;
4042

@@ -57,6 +59,8 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo
5759

5860
private String postLogoutRedirectUri;
5961

62+
private Converter<RedirectUriParameters, Mono<String>> redirectUriResolver = new DefaultRedirectUriResolver();
63+
6064
/**
6165
* Constructs an {@link OidcClientInitiatedServerLogoutSuccessHandler} with the
6266
* provided parameters
@@ -72,22 +76,11 @@ public OidcClientInitiatedServerLogoutSuccessHandler(
7276

7377
@Override
7478
public Mono<Void> onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) {
79+
RedirectUriParameters redirectUriParameters = new RedirectUriParameters();
80+
redirectUriParameters.setAuthentication(authentication);
81+
redirectUriParameters.setServerWebExchange(exchange.getExchange());
7582
// @formatter:off
76-
return Mono.just(authentication)
77-
.filter(OAuth2AuthenticationToken.class::isInstance)
78-
.filter((token) -> authentication.getPrincipal() instanceof OidcUser)
79-
.map(OAuth2AuthenticationToken.class::cast)
80-
.map(OAuth2AuthenticationToken::getAuthorizedClientRegistrationId)
81-
.flatMap(this.clientRegistrationRepository::findByRegistrationId)
82-
.flatMap((clientRegistration) -> {
83-
URI endSessionEndpoint = endSessionEndpoint(clientRegistration);
84-
if (endSessionEndpoint == null) {
85-
return Mono.empty();
86-
}
87-
String idToken = idToken(authentication);
88-
String postLogoutRedirectUri = postLogoutRedirectUri(exchange.getExchange().getRequest(), clientRegistration);
89-
return Mono.just(endpointUri(endSessionEndpoint, idToken, postLogoutRedirectUri));
90-
})
83+
return this.redirectUriResolver.convert(redirectUriParameters)
9184
.switchIfEmpty(
9285
this.serverLogoutSuccessHandler.onLogoutSuccess(exchange, authentication).then(Mono.empty())
9386
)
@@ -189,4 +182,90 @@ public void setLogoutSuccessUrl(URI logoutSuccessUrl) {
189182
this.serverLogoutSuccessHandler.setLogoutSuccessUrl(logoutSuccessUrl);
190183
}
191184

185+
/**
186+
* Set the {@link Converter} that converts {@link RedirectUriParameters} to redirect
187+
* URI
188+
* @param redirectUriResolver {@link Converter}
189+
* @since 6.4
190+
*/
191+
public void setRedirectUriResolver(Converter<RedirectUriParameters, Mono<String>> redirectUriResolver) {
192+
Assert.notNull(redirectUriResolver, "redirectUriResolver cannot be null");
193+
this.redirectUriResolver = redirectUriResolver;
194+
}
195+
196+
/**
197+
* Parameters, required for redirect URI resolving.
198+
*
199+
* @author Max Batischev
200+
* @since 6.4
201+
*/
202+
public static class RedirectUriParameters {
203+
204+
private ServerWebExchange serverWebExchange;
205+
206+
private Authentication authentication;
207+
208+
private ClientRegistration clientRegistration;
209+
210+
public ServerWebExchange getServerWebExchange() {
211+
return this.serverWebExchange;
212+
}
213+
214+
public Authentication getAuthentication() {
215+
return this.authentication;
216+
}
217+
218+
public ClientRegistration getClientRegistration() {
219+
return this.clientRegistration;
220+
}
221+
222+
public void setClientRegistration(ClientRegistration clientRegistration) {
223+
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
224+
this.clientRegistration = clientRegistration;
225+
}
226+
227+
public void setServerWebExchange(ServerWebExchange serverWebExchange) {
228+
Assert.notNull(serverWebExchange, "serverWebExchange cannot be null");
229+
this.serverWebExchange = serverWebExchange;
230+
}
231+
232+
public void setAuthentication(Authentication authentication) {
233+
Assert.notNull(authentication, "authentication cannot be null");
234+
this.authentication = authentication;
235+
}
236+
237+
}
238+
239+
/**
240+
* Default {@link Converter} for redirect uri resolving.
241+
*
242+
* @since 6.4
243+
*/
244+
private final class DefaultRedirectUriResolver implements Converter<RedirectUriParameters, Mono<String>> {
245+
246+
@Override
247+
public Mono<String> convert(RedirectUriParameters redirectUriParameters) {
248+
// @formatter:off
249+
return Mono.just(redirectUriParameters.authentication)
250+
.filter(OAuth2AuthenticationToken.class::isInstance)
251+
.filter((token) -> redirectUriParameters.authentication.getPrincipal() instanceof OidcUser)
252+
.map(OAuth2AuthenticationToken.class::cast)
253+
.map(OAuth2AuthenticationToken::getAuthorizedClientRegistrationId)
254+
.flatMap(
255+
OidcClientInitiatedServerLogoutSuccessHandler.this.clientRegistrationRepository::findByRegistrationId)
256+
.flatMap((clientRegistration) -> {
257+
URI endSessionEndpoint = endSessionEndpoint(clientRegistration);
258+
if (endSessionEndpoint == null) {
259+
return Mono.empty();
260+
}
261+
String idToken = idToken(redirectUriParameters.authentication);
262+
String postLogoutRedirectUri = postLogoutRedirectUri(
263+
redirectUriParameters.serverWebExchange.getRequest(), clientRegistration);
264+
return Mono.just(endpointUri(endSessionEndpoint, idToken, postLogoutRedirectUri));
265+
});
266+
// @formatter:on
267+
}
268+
269+
}
270+
192271
}

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.io.IOException;
2020
import java.net.URI;
2121
import java.util.Collections;
22+
import java.util.Objects;
2223

2324
import jakarta.servlet.ServletException;
2425
import org.junit.jupiter.api.BeforeEach;
@@ -199,6 +200,25 @@ public void setPostLogoutRedirectUriTemplateWhenGivenNullThenThrowsException() {
199200
assertThatIllegalArgumentException().isThrownBy(() -> this.handler.setPostLogoutRedirectUri((String) null));
200201
}
201202

203+
@Test
204+
public void logoutWhenCustomRedirectUriResolverSetThenRedirects() {
205+
OAuth2AuthenticationToken token = new OAuth2AuthenticationToken(TestOidcUsers.create(),
206+
AuthorityUtils.NO_AUTHORITIES, this.registration.getRegistrationId());
207+
WebFilterExchange filterExchange = new WebFilterExchange(this.exchange, this.chain);
208+
given(this.exchange.getRequest())
209+
.willReturn(MockServerHttpRequest.get("/").queryParam("location", "https://test.com").build());
210+
// @formatter:off
211+
this.handler.setRedirectUriResolver((params) -> Mono.just(
212+
Objects.requireNonNull(params.getServerWebExchange()
213+
.getRequest()
214+
.getQueryParams()
215+
.getFirst("location"))));
216+
// @formatter:on
217+
this.handler.onLogoutSuccess(filterExchange, token).block();
218+
219+
assertThat(redirectedUrl(this.exchange)).isEqualTo("https://test.com");
220+
}
221+
202222
private String redirectedUrl(ServerWebExchange exchange) {
203223
return exchange.getResponse().getHeaders().getFirst("Location");
204224
}

0 commit comments

Comments
 (0)