Skip to content

Commit

Permalink
Allow to set an authentication manager for back-channel logout in Ser…
Browse files Browse the repository at this point in the history
…verHttpSecurity
  • Loading branch information
ErwinSteffens committed Feb 7, 2025
1 parent 4776446 commit 6accba9
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@
* "https://openid.net/specs/openid-connect-backchannel-1_0.html">OIDC Back-Channel
* Logout</a>
*/
final class OidcBackChannelLogoutReactiveAuthenticationManager implements ReactiveAuthenticationManager {
public final class OidcBackChannelLogoutReactiveAuthenticationManager implements ReactiveAuthenticationManager {

private ReactiveJwtDecoderFactory<ClientRegistration> logoutTokenDecoderFactory;

/**
* Construct an {@link OidcBackChannelLogoutReactiveAuthenticationManager}
*/
OidcBackChannelLogoutReactiveAuthenticationManager() {
public OidcBackChannelLogoutReactiveAuthenticationManager() {
Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidator = (clientRegistration) -> JwtValidators
.createDefaultWithValidators(new OidcBackChannelLogoutTokenValidator(clientRegistration));
this.logoutTokenDecoderFactory = (clientRegistration) -> {
Expand Down Expand Up @@ -130,7 +130,7 @@ private Mono<Jwt> decode(ClientRegistration registration, String token) {
* correspond to the {@link ClientRegistration} associated with the OIDC logout token.
* @param logoutTokenDecoderFactory the {@link JwtDecoderFactory} to use
*/
void setLogoutTokenDecoderFactory(ReactiveJwtDecoderFactory<ClientRegistration> logoutTokenDecoderFactory) {
public void setLogoutTokenDecoderFactory(ReactiveJwtDecoderFactory<ClientRegistration> logoutTokenDecoderFactory) {
Assert.notNull(logoutTokenDecoderFactory, "logoutTokenDecoderFactory cannot be null");
this.logoutTokenDecoderFactory = logoutTokenDecoderFactory;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
* "https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation">the OIDC
* Back-Channel Logout spec</a>
*/
final class OidcBackChannelLogoutTokenValidator implements OAuth2TokenValidator<Jwt> {
public final class OidcBackChannelLogoutTokenValidator implements OAuth2TokenValidator<Jwt> {

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

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

private final String issuer;

OidcBackChannelLogoutTokenValidator(ClientRegistration clientRegistration) {
public OidcBackChannelLogoutTokenValidator(ClientRegistration clientRegistration) {
this.audience = clientRegistration.getClientId();
String issuer = clientRegistration.getProviderDetails().getIssuerUri();
Assert.hasText(issuer, "Provider issuer cannot be null");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5600,7 +5600,7 @@ public final class BackChannelLogoutConfigurer {

private ServerAuthenticationConverter authenticationConverter;

private final ReactiveAuthenticationManager authenticationManager = new OidcBackChannelLogoutReactiveAuthenticationManager();
private ReactiveAuthenticationManager authenticationManager;

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

Expand All @@ -5613,6 +5613,9 @@ private ServerAuthenticationConverter authenticationConverter() {
}

private ReactiveAuthenticationManager authenticationManager() {
if (this.authenticationManager == null) {
this.authenticationManager = new OidcBackChannelLogoutReactiveAuthenticationManager();
}
return this.authenticationManager;
}

Expand Down Expand Up @@ -5745,6 +5748,41 @@ public BackChannelLogoutConfigurer logoutHandler(ServerLogoutHandler logoutHandl
return this;
}

/**
* Configure a custom instance of the authentication manager used for
* back-channel logout.
*
* <p>
* By default, a new instance of
* {@link OidcBackChannelLogoutReactiveAuthenticationManager} will be created.
* If you want to customize the authentication manager, you can use this
* method.
*
* <p>
* For example, if you want to customize the WebClient instance for fetching
* the JWKS keys in the logout process, you can configure Back-Channel Logout
* in the following way:
*
* <pre>
* http
* .oidcLogout((oidc) -&gt; oidc
* .backChannel(config -> {
* var logoutTokenDecoderFactory = new CustomOidcLogoutTokenDecoderFactory();
* var manager = new OidcBackChannelLogoutReactiveAuthenticationManager();
* manager.setLogoutTokenDecoderFactory(logoutTokenDecoderFactory);
* config.authenticationManager(manager);
* }))
* );
* </pre>
* @param authenticationManager the {@link ReactiveAuthenticationManager} to
* use as replacement of the default used authentication manager
* @return {@link BackChannelLogoutConfigurer} for further customizations
* @since 6.5
*/
public void setAuthenticationManager(ReactiveAuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}

void configure(ServerHttpSecurity http) {
ServerLogoutHandler oidcLogout = this.logoutHandler.get();
ServerLogoutHandler sessionLogout = new SecurityContextServerLogoutHandler();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,35 @@ void logoutWhenCustomComponentsThenUses() {
verify(sessionRegistry, atLeastOnce()).removeSessionInformation(any(OidcLogoutToken.class));
}

@Test
void logoutCustomAuthenticationManagerThenUses() {
this.spring
.register(WebServerConfig.class, OidcProviderConfig.class, WithCustomAuthenticationManagerConfig.class)
.autowire();
String registrationId = this.clientRegistration.getRegistrationId();
String sessionId = login();
String logoutToken = this.test.get()
.uri("/token/logout")
.cookie("SESSION", sessionId)
.exchange()
.expectStatus()
.isOk()
.returnResult(String.class)
.getResponseBody()
.blockFirst();
this.test.post()
.uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
.body(BodyInserters.fromFormData("logout_token", logoutToken))
.exchange()
.expectStatus()
.isOk();
this.test.get().uri("/token/logout").cookie("SESSION", sessionId).exchange().expectStatus().isUnauthorized();
ReactiveOidcSessionRegistry sessionRegistry = this.spring.getContext()
.getBean(ReactiveOidcSessionRegistry.class);
verify(sessionRegistry, atLeastOnce()).saveSessionInformation(any());
verify(sessionRegistry, atLeastOnce()).removeSessionInformation(any(OidcLogoutToken.class));
}

@Test
void logoutWhenProviderIssuerMissingThen5xxServerError() {
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, ProviderIssuerMissingConfig.class)
Expand Down Expand Up @@ -628,6 +657,35 @@ ReactiveOidcSessionRegistry sessionRegistry() {

}

@Configuration
@EnableWebFluxSecurity
@Import(RegistrationConfig.class)
static class WithCustomAuthenticationManagerConfig {

ReactiveOidcSessionRegistry sessionRegistry = spy(new InMemoryReactiveOidcSessionRegistry());

@Bean
@Order(1)
SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
.oauth2Login(Customizer.withDefaults())
.oidcLogout((oidc) -> oidc.backChannel((backChannel) -> {
backChannel.setAuthenticationManager(new OidcBackChannelLogoutReactiveAuthenticationManager());
}));
// @formatter:on

return http.build();
}

@Bean
ReactiveOidcSessionRegistry sessionRegistry() {
return this.sessionRegistry;
}

}

@Configuration
@EnableWebFluxSecurity
@Import(RegistrationConfig.class)
Expand Down

0 comments on commit 6accba9

Please sign in to comment.