Skip to content

Commit 6accba9

Browse files
committed
Allow to set an authentication manager for back-channel logout in ServerHttpSecurity
1 parent 4776446 commit 6accba9

File tree

4 files changed

+102
-6
lines changed

4 files changed

+102
-6
lines changed

config/src/main/java/org/springframework/security/config/web/server/OidcBackChannelLogoutReactiveAuthenticationManager.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,14 @@
6464
* "https://openid.net/specs/openid-connect-backchannel-1_0.html">OIDC Back-Channel
6565
* Logout</a>
6666
*/
67-
final class OidcBackChannelLogoutReactiveAuthenticationManager implements ReactiveAuthenticationManager {
67+
public final class OidcBackChannelLogoutReactiveAuthenticationManager implements ReactiveAuthenticationManager {
6868

6969
private ReactiveJwtDecoderFactory<ClientRegistration> logoutTokenDecoderFactory;
7070

7171
/**
7272
* Construct an {@link OidcBackChannelLogoutReactiveAuthenticationManager}
7373
*/
74-
OidcBackChannelLogoutReactiveAuthenticationManager() {
74+
public OidcBackChannelLogoutReactiveAuthenticationManager() {
7575
Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidator = (clientRegistration) -> JwtValidators
7676
.createDefaultWithValidators(new OidcBackChannelLogoutTokenValidator(clientRegistration));
7777
this.logoutTokenDecoderFactory = (clientRegistration) -> {
@@ -130,7 +130,7 @@ private Mono<Jwt> decode(ClientRegistration registration, String token) {
130130
* correspond to the {@link ClientRegistration} associated with the OIDC logout token.
131131
* @param logoutTokenDecoderFactory the {@link JwtDecoderFactory} to use
132132
*/
133-
void setLogoutTokenDecoderFactory(ReactiveJwtDecoderFactory<ClientRegistration> logoutTokenDecoderFactory) {
133+
public void setLogoutTokenDecoderFactory(ReactiveJwtDecoderFactory<ClientRegistration> logoutTokenDecoderFactory) {
134134
Assert.notNull(logoutTokenDecoderFactory, "logoutTokenDecoderFactory cannot be null");
135135
this.logoutTokenDecoderFactory = logoutTokenDecoderFactory;
136136
}

config/src/main/java/org/springframework/security/config/web/server/OidcBackChannelLogoutTokenValidator.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
* "https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation">the OIDC
4747
* Back-Channel Logout spec</a>
4848
*/
49-
final class OidcBackChannelLogoutTokenValidator implements OAuth2TokenValidator<Jwt> {
49+
public final class OidcBackChannelLogoutTokenValidator implements OAuth2TokenValidator<Jwt> {
5050

5151
private static final String LOGOUT_VALIDATION_URL = "https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation";
5252

@@ -56,7 +56,7 @@ final class OidcBackChannelLogoutTokenValidator implements OAuth2TokenValidator<
5656

5757
private final String issuer;
5858

59-
OidcBackChannelLogoutTokenValidator(ClientRegistration clientRegistration) {
59+
public OidcBackChannelLogoutTokenValidator(ClientRegistration clientRegistration) {
6060
this.audience = clientRegistration.getClientId();
6161
String issuer = clientRegistration.getProviderDetails().getIssuerUri();
6262
Assert.hasText(issuer, "Provider issuer cannot be null");

config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

+39-1
Original file line numberDiff line numberDiff line change
@@ -5600,7 +5600,7 @@ public final class BackChannelLogoutConfigurer {
56005600

56015601
private ServerAuthenticationConverter authenticationConverter;
56025602

5603-
private final ReactiveAuthenticationManager authenticationManager = new OidcBackChannelLogoutReactiveAuthenticationManager();
5603+
private ReactiveAuthenticationManager authenticationManager;
56045604

56055605
private Supplier<ServerLogoutHandler> logoutHandler = this::logoutHandler;
56065606

@@ -5613,6 +5613,9 @@ private ServerAuthenticationConverter authenticationConverter() {
56135613
}
56145614

56155615
private ReactiveAuthenticationManager authenticationManager() {
5616+
if (this.authenticationManager == null) {
5617+
this.authenticationManager = new OidcBackChannelLogoutReactiveAuthenticationManager();
5618+
}
56165619
return this.authenticationManager;
56175620
}
56185621

@@ -5745,6 +5748,41 @@ public BackChannelLogoutConfigurer logoutHandler(ServerLogoutHandler logoutHandl
57455748
return this;
57465749
}
57475750

5751+
/**
5752+
* Configure a custom instance of the authentication manager used for
5753+
* back-channel logout.
5754+
*
5755+
* <p>
5756+
* By default, a new instance of
5757+
* {@link OidcBackChannelLogoutReactiveAuthenticationManager} will be created.
5758+
* If you want to customize the authentication manager, you can use this
5759+
* method.
5760+
*
5761+
* <p>
5762+
* For example, if you want to customize the WebClient instance for fetching
5763+
* the JWKS keys in the logout process, you can configure Back-Channel Logout
5764+
* in the following way:
5765+
*
5766+
* <pre>
5767+
* http
5768+
* .oidcLogout((oidc) -&gt; oidc
5769+
* .backChannel(config -> {
5770+
* var logoutTokenDecoderFactory = new CustomOidcLogoutTokenDecoderFactory();
5771+
* var manager = new OidcBackChannelLogoutReactiveAuthenticationManager();
5772+
* manager.setLogoutTokenDecoderFactory(logoutTokenDecoderFactory);
5773+
* config.authenticationManager(manager);
5774+
* }))
5775+
* );
5776+
* </pre>
5777+
* @param authenticationManager the {@link ReactiveAuthenticationManager} to
5778+
* use as replacement of the default used authentication manager
5779+
* @return {@link BackChannelLogoutConfigurer} for further customizations
5780+
* @since 6.5
5781+
*/
5782+
public void setAuthenticationManager(ReactiveAuthenticationManager authenticationManager) {
5783+
this.authenticationManager = authenticationManager;
5784+
}
5785+
57485786
void configure(ServerHttpSecurity http) {
57495787
ServerLogoutHandler oidcLogout = this.logoutHandler.get();
57505788
ServerLogoutHandler sessionLogout = new SecurityContextServerLogoutHandler();

config/src/test/java/org/springframework/security/config/web/server/OidcLogoutSpecTests.java

+58
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,35 @@ void logoutWhenCustomComponentsThenUses() {
379379
verify(sessionRegistry, atLeastOnce()).removeSessionInformation(any(OidcLogoutToken.class));
380380
}
381381

382+
@Test
383+
void logoutCustomAuthenticationManagerThenUses() {
384+
this.spring
385+
.register(WebServerConfig.class, OidcProviderConfig.class, WithCustomAuthenticationManagerConfig.class)
386+
.autowire();
387+
String registrationId = this.clientRegistration.getRegistrationId();
388+
String sessionId = login();
389+
String logoutToken = this.test.get()
390+
.uri("/token/logout")
391+
.cookie("SESSION", sessionId)
392+
.exchange()
393+
.expectStatus()
394+
.isOk()
395+
.returnResult(String.class)
396+
.getResponseBody()
397+
.blockFirst();
398+
this.test.post()
399+
.uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
400+
.body(BodyInserters.fromFormData("logout_token", logoutToken))
401+
.exchange()
402+
.expectStatus()
403+
.isOk();
404+
this.test.get().uri("/token/logout").cookie("SESSION", sessionId).exchange().expectStatus().isUnauthorized();
405+
ReactiveOidcSessionRegistry sessionRegistry = this.spring.getContext()
406+
.getBean(ReactiveOidcSessionRegistry.class);
407+
verify(sessionRegistry, atLeastOnce()).saveSessionInformation(any());
408+
verify(sessionRegistry, atLeastOnce()).removeSessionInformation(any(OidcLogoutToken.class));
409+
}
410+
382411
@Test
383412
void logoutWhenProviderIssuerMissingThen5xxServerError() {
384413
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, ProviderIssuerMissingConfig.class)
@@ -628,6 +657,35 @@ ReactiveOidcSessionRegistry sessionRegistry() {
628657

629658
}
630659

660+
@Configuration
661+
@EnableWebFluxSecurity
662+
@Import(RegistrationConfig.class)
663+
static class WithCustomAuthenticationManagerConfig {
664+
665+
ReactiveOidcSessionRegistry sessionRegistry = spy(new InMemoryReactiveOidcSessionRegistry());
666+
667+
@Bean
668+
@Order(1)
669+
SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception {
670+
// @formatter:off
671+
http
672+
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
673+
.oauth2Login(Customizer.withDefaults())
674+
.oidcLogout((oidc) -> oidc.backChannel((backChannel) -> {
675+
backChannel.setAuthenticationManager(new OidcBackChannelLogoutReactiveAuthenticationManager());
676+
}));
677+
// @formatter:on
678+
679+
return http.build();
680+
}
681+
682+
@Bean
683+
ReactiveOidcSessionRegistry sessionRegistry() {
684+
return this.sessionRegistry;
685+
}
686+
687+
}
688+
631689
@Configuration
632690
@EnableWebFluxSecurity
633691
@Import(RegistrationConfig.class)

0 commit comments

Comments
 (0)