Skip to content

Commit ed3f3d1

Browse files
Max Batischevjzheaux
authored andcommitted
Add support customizing redirect URI
Closes gh-14778
1 parent 7b8ff72 commit ed3f3d1

File tree

2 files changed

+105
-11
lines changed

2 files changed

+105
-11
lines changed

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

Lines changed: 84 additions & 10 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
@@ -79,15 +83,10 @@ public Mono<Void> onLogoutSuccess(WebFilterExchange exchange, Authentication aut
7983
.map(OAuth2AuthenticationToken.class::cast)
8084
.map(OAuth2AuthenticationToken::getAuthorizedClientRegistrationId)
8185
.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-
})
86+
.flatMap((clientRegistration) ->
87+
this.redirectUriResolver.convert(
88+
new RedirectUriParameters(exchange.getExchange(), authentication, clientRegistration))
89+
)
9190
.switchIfEmpty(
9291
this.serverLogoutSuccessHandler.onLogoutSuccess(exchange, authentication).then(Mono.empty())
9392
)
@@ -189,4 +188,79 @@ public void setLogoutSuccessUrl(URI logoutSuccessUrl) {
189188
this.serverLogoutSuccessHandler.setLogoutSuccessUrl(logoutSuccessUrl);
190189
}
191190

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

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)