From 8babcd387dc9019f86981b4672c439c4fe9caf68 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 21 Sep 2023 11:18:13 +0200 Subject: [PATCH] Introduce `AuthenticationEventMulticaster` interface. Closes gh-816 --- .../AuthenticationEventPublisher.java | 37 +++++----- .../LifecycleAwareSessionManager.java | 22 +++--- .../ReactiveLifecycleAwareSessionManager.java | 25 +++---- .../event/AuthenticationEventMulticaster.java | 71 +++++++++++++++++++ .../AbstractReactiveVaultConfiguration.java | 57 ++++++++++++++- 5 files changed, 167 insertions(+), 45 deletions(-) create mode 100644 spring-vault-core/src/main/java/org/springframework/vault/authentication/event/AuthenticationEventMulticaster.java diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationEventPublisher.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationEventPublisher.java index 073658b7..5a1a7993 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationEventPublisher.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationEventPublisher.java @@ -22,6 +22,7 @@ import org.springframework.util.Assert; import org.springframework.vault.authentication.event.AuthenticationErrorEvent; import org.springframework.vault.authentication.event.AuthenticationErrorListener; import org.springframework.vault.authentication.event.AuthenticationEvent; +import org.springframework.vault.authentication.event.AuthenticationEventMulticaster; import org.springframework.vault.authentication.event.AuthenticationListener; /** @@ -31,13 +32,13 @@ import org.springframework.vault.authentication.event.AuthenticationListener; * {@link AuthenticationErrorListener}. * * @author Mark Paluch - * @since 2.2 * @see AuthenticationEvent * @see AuthenticationErrorEvent * @see AuthenticationListener * @see AuthenticationErrorListener + * @since 2.2 */ -public abstract class AuthenticationEventPublisher { +public abstract class AuthenticationEventPublisher implements AuthenticationEventMulticaster { private final Set listeners = new CopyOnWriteArraySet<>(); @@ -46,8 +47,9 @@ public abstract class AuthenticationEventPublisher { /** * Add a {@link AuthenticationListener}. The listener starts receiving events as soon * as possible. - * @param listener lease listener, must not be {@literal null}. + * @param listener the listener, must not be {@literal null}. */ + @Override public void addAuthenticationListener(AuthenticationListener listener) { Assert.notNull(listener, "AuthenticationEventListener must not be null"); @@ -57,8 +59,9 @@ public abstract class AuthenticationEventPublisher { /** * Remove a {@link AuthenticationListener}. - * @param listener must not be {@literal null}. + * @param listener the listener, must not be {@literal null}. */ + @Override public void removeAuthenticationListener(AuthenticationListener listener) { this.listeners.remove(listener); } @@ -66,8 +69,9 @@ public abstract class AuthenticationEventPublisher { /** * Add a {@link AuthenticationErrorListener}. The listener starts receiving events as * soon as possible. - * @param listener lease listener, must not be {@literal null}. + * @param listener the listener, must not be {@literal null}. */ + @Override public void addErrorListener(AuthenticationErrorListener listener) { Assert.notNull(listener, "AuthenticationEventErrorListener must not be null"); @@ -77,31 +81,24 @@ public abstract class AuthenticationEventPublisher { /** * Remove a {@link AuthenticationErrorListener}. - * @param listener must not be {@literal null}. + * @param listener the listener, must not be {@literal null}. */ + @Override public void removeErrorListener(AuthenticationErrorListener listener) { this.errorListeners.remove(listener); } - /** - * Dispatch the event to all {@link AuthenticationListener}s. - * @param authenticationEvent the event to dispatch. - */ - void dispatch(AuthenticationEvent authenticationEvent) { - + @Override + public void multicastEvent(AuthenticationEvent event) { for (AuthenticationListener listener : this.listeners) { - listener.onAuthenticationEvent(authenticationEvent); + listener.onAuthenticationEvent(event); } } - /** - * Dispatch the event to all {@link AuthenticationErrorListener}s. - * @param authenticationEvent the event to dispatch. - */ - void dispatch(AuthenticationErrorEvent authenticationEvent) { - + @Override + public void multicastEvent(AuthenticationErrorEvent event) { for (AuthenticationErrorListener listener : this.errorListeners) { - listener.onAuthenticationError(authenticationEvent); + listener.onAuthenticationError(event); } } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/LifecycleAwareSessionManager.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LifecycleAwareSessionManager.java index 63eda248..e97a9f2f 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/LifecycleAwareSessionManager.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LifecycleAwareSessionManager.java @@ -169,10 +169,10 @@ public class LifecycleAwareSessionManager extends LifecycleAwareSessionManagerSu protected void revoke(VaultToken token) { try { - dispatch(new BeforeLoginTokenRevocationEvent(token)); + multicastEvent(new BeforeLoginTokenRevocationEvent(token)); this.restOperations.postForObject("auth/token/revoke-self", new HttpEntity<>(VaultHttpHeaders.from(token)), Map.class); - dispatch(new AfterLoginTokenRevocationEvent(token)); + multicastEvent(new AfterLoginTokenRevocationEvent(token)); } catch (RuntimeException e) { if (LoginToken.hasAccessor(token)) { @@ -183,7 +183,7 @@ public class LifecycleAwareSessionManager extends LifecycleAwareSessionManagerSu else { this.logger.warn("Cannot revoke VaultToken", e); } - dispatch(new LoginTokenRevocationFailedEvent(token, e)); + multicastEvent(new LoginTokenRevocationFailedEvent(token, e)); } } @@ -229,14 +229,14 @@ public class LifecycleAwareSessionManager extends LifecycleAwareSessionManagerSu this.logger.warn(exception.getMessage()); } - dispatch(new LoginTokenRenewalFailedEvent(tokenWrapper.getToken(), exception)); + multicastEvent(new LoginTokenRenewalFailedEvent(tokenWrapper.getToken(), exception)); return shouldDrop ? RenewOutcome.TERMINAL_ERROR : RenewOutcome.RENEWABLE_ERROR; } } private RenewOutcome doRenew(TokenWrapper wrapper) { - dispatch(new BeforeLoginTokenRenewedEvent(wrapper.getToken())); + multicastEvent(new BeforeLoginTokenRenewedEvent(wrapper.getToken())); VaultResponse vaultResponse = this.restOperations.postForObject("auth/token/renew-self", new HttpEntity<>(VaultHttpHeaders.from(wrapper.token)), VaultResponse.class); @@ -254,12 +254,12 @@ public class LifecycleAwareSessionManager extends LifecycleAwareSessionManagerSu } setToken(Optional.empty()); - dispatch(new LoginTokenExpiredEvent(renewed)); + multicastEvent(new LoginTokenExpiredEvent(renewed)); return RenewOutcome.TERMINAL_ERROR; } setToken(Optional.of(new TokenWrapper(renewed, wrapper.revocable))); - dispatch(new AfterLoginTokenRenewedEvent(renewed)); + multicastEvent(new AfterLoginTokenRenewedEvent(renewed)); return RenewOutcome.SUCCESS; } @@ -292,7 +292,7 @@ public class LifecycleAwareSessionManager extends LifecycleAwareSessionManagerSu token = this.clientAuthentication.login(); } catch (VaultException e) { - dispatch(new LoginFailedEvent(this.clientAuthentication, e)); + multicastEvent(new LoginFailedEvent(this.clientAuthentication, e)); throw e; } @@ -305,12 +305,12 @@ public class LifecycleAwareSessionManager extends LifecycleAwareSessionManagerSu } catch (VaultTokenLookupException e) { this.logger.warn(String.format("Cannot enhance VaultToken to a LoginToken: %s", e.getMessage())); - dispatch(new AuthenticationErrorEvent(token, e)); + multicastEvent(new AuthenticationErrorEvent(token, e)); } } setToken(Optional.of(wrapper)); - dispatch(new AfterLoginEvent(token)); + multicastEvent(new AfterLoginEvent(token)); if (isTokenRenewable()) { scheduleRenewal(); @@ -356,7 +356,7 @@ public class LifecycleAwareSessionManager extends LifecycleAwareSessionManagerSu } catch (Exception e) { this.logger.error("Cannot renew VaultToken", e); - dispatch(new LoginTokenRenewalFailedEvent(token, e)); + multicastEvent(new LoginTokenRenewalFailedEvent(token, e)); } }; diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/ReactiveLifecycleAwareSessionManager.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/ReactiveLifecycleAwareSessionManager.java index 519d536a..0bc7ff13 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/ReactiveLifecycleAwareSessionManager.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/ReactiveLifecycleAwareSessionManager.java @@ -185,8 +185,8 @@ public class ReactiveLifecycleAwareSessionManager extends LifecycleAwareSessionM }) .retrieve() .bodyToMono(String.class) - .doOnSubscribe(ignore -> dispatch(new BeforeLoginTokenRevocationEvent(token))) - .doOnNext(ignore -> dispatch(new AfterLoginTokenRevocationEvent(token))) + .doOnSubscribe(ignore -> multicastEvent(new BeforeLoginTokenRevocationEvent(token))) + .doOnNext(ignore -> multicastEvent(new AfterLoginTokenRevocationEvent(token))) .onErrorResume(WebClientResponseException.class, e -> onRevokeFailed(token, e)) .onErrorResume(Exception.class, e -> onRevokeFailed(token, e)) .then(); @@ -202,7 +202,7 @@ public class ReactiveLifecycleAwareSessionManager extends LifecycleAwareSessionM this.logger.warn("Cannot revoke VaultToken", e); } - dispatch(new LoginTokenRevocationFailedEvent(token, e)); + multicastEvent(new LoginTokenRevocationFailedEvent(token, e)); return Mono.empty(); } @@ -251,7 +251,7 @@ public class ReactiveLifecycleAwareSessionManager extends LifecycleAwareSessionM this.logger.warn(exception.getMessage()); } - dispatch(new LoginTokenRenewalFailedEvent(wrapper.getToken(), exception)); + multicastEvent(new LoginTokenRenewalFailedEvent(wrapper.getToken(), exception)); return shouldDrop ? EMPTY : Mono.just(wrapper); }); } @@ -264,14 +264,15 @@ public class ReactiveLifecycleAwareSessionManager extends LifecycleAwareSessionM .retrieve() .bodyToMono(VaultResponse.class); - return exchange.doOnSubscribe(ignore -> dispatch(new BeforeLoginTokenRenewedEvent(tokenWrapper.getToken()))) + return exchange + .doOnSubscribe(ignore -> multicastEvent(new BeforeLoginTokenRenewedEvent(tokenWrapper.getToken()))) .handle((response, sink) -> { LoginToken renewed = LoginTokenUtil.from(response.getRequiredAuth()); if (!isExpired(renewed)) { sink.next(new TokenWrapper(renewed, tokenWrapper.revocable)); - dispatch(new AfterLoginTokenRenewedEvent(renewed)); + multicastEvent(new AfterLoginTokenRenewedEvent(renewed)); return; } @@ -287,7 +288,7 @@ public class ReactiveLifecycleAwareSessionManager extends LifecycleAwareSessionM } dropCurrentToken(); - dispatch(new LoginTokenExpiredEvent(renewed)); + multicastEvent(new LoginTokenExpiredEvent(renewed)); }); } @@ -310,7 +311,7 @@ public class ReactiveLifecycleAwareSessionManager extends LifecycleAwareSessionM Mono obtainToken = this.clientAuthentication.getVaultToken() .flatMap(this::doSelfLookup) // .onErrorMap(it -> { - dispatch(new LoginFailedEvent(this.clientAuthentication, it)); + multicastEvent(new LoginFailedEvent(this.clientAuthentication, it)); return it; }) .doOnNext(it -> { @@ -319,7 +320,7 @@ public class ReactiveLifecycleAwareSessionManager extends LifecycleAwareSessionM scheduleRenewal(it.getToken()); } - dispatch(new AfterLoginEvent(it.getToken())); + multicastEvent(new AfterLoginEvent(it.getToken())); }); this.token.compareAndSet(tokenWrapper, obtainToken.cache()); @@ -339,7 +340,7 @@ public class ReactiveLifecycleAwareSessionManager extends LifecycleAwareSessionM return loginTokenMono.onErrorResume(e -> { this.logger.warn(String.format("Cannot enhance VaultToken to a LoginToken: %s", e.getMessage())); - dispatch(new AuthenticationErrorEvent(token, e)); + multicastEvent(new AuthenticationErrorEvent(token, e)); return Mono.just(token); }).map(it -> new TokenWrapper(it, false)); } @@ -379,13 +380,13 @@ public class ReactiveLifecycleAwareSessionManager extends LifecycleAwareSessionM if (isTokenRenewable(token)) { renewToken().subscribe(this::scheduleRenewal, e -> { this.logger.error("Cannot renew VaultToken", e); - dispatch(new LoginTokenRenewalFailedEvent(token, e)); + multicastEvent(new LoginTokenRenewalFailedEvent(token, e)); }); } } catch (Exception e) { this.logger.error("Cannot renew VaultToken", e); - dispatch(new LoginTokenRenewalFailedEvent(token, e)); + multicastEvent(new LoginTokenRenewalFailedEvent(token, e)); } }; diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/event/AuthenticationEventMulticaster.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/event/AuthenticationEventMulticaster.java new file mode 100644 index 00000000..4bca3865 --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/event/AuthenticationEventMulticaster.java @@ -0,0 +1,71 @@ +/* + * Copyright 2023 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.vault.authentication.event; + +/** + * Interface to be implemented by objects that can manage a number of + * {@link AuthenticationEvent} and {@link AuthenticationErrorEvent} objects and publish + * events to them. + *

+ * An {@link org.springframework.vault.authentication.AuthenticationEventPublisher}, + * typically a lifecycle-aware + * {@link org.springframework.vault.authentication.SessionManager}, can use an + * {@code AuthenticationEventMulticaster} as a delegate for actually publishing events. + * + * @author Mark Paluch + * @see AuthenticationListener + * @see AuthenticationErrorListener + * @since 3.1 + */ +public interface AuthenticationEventMulticaster { + + /** + * Add a {@link AuthenticationListener}. + * @param listener the listener, must not be {@literal null}. + */ + void addAuthenticationListener(AuthenticationListener listener); + + /** + * Remove a {@link AuthenticationListener}. + * @param listener the listener, must not be {@literal null}. + */ + void removeAuthenticationListener(AuthenticationListener listener); + + /** + * Add a {@link AuthenticationErrorListener}. + * @param listener the listener, must not be {@literal null}. + */ + void addErrorListener(AuthenticationErrorListener listener); + + /** + * Remove a {@link AuthenticationErrorListener}. + * @param listener the listener, must not be {@literal null}. + */ + void removeErrorListener(AuthenticationErrorListener listener); + + /** + * Multicast the given application event to appropriate listeners. + * @param event the event to multicast. + */ + void multicastEvent(AuthenticationEvent event); + + /** + * Multicast the given application event to appropriate listeners. + * @param event the event to multicast. + */ + void multicastEvent(AuthenticationErrorEvent event); + +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java b/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java index 9ca4cecf..19013b10 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java @@ -24,6 +24,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.util.Assert; +import org.springframework.vault.authentication.AuthenticationEventPublisher; import org.springframework.vault.authentication.AuthenticationStepsFactory; import org.springframework.vault.authentication.AuthenticationStepsOperator; import org.springframework.vault.authentication.CachingVaultTokenSupplier; @@ -33,6 +34,11 @@ import org.springframework.vault.authentication.ReactiveSessionManager; import org.springframework.vault.authentication.SessionManager; import org.springframework.vault.authentication.TokenAuthentication; import org.springframework.vault.authentication.VaultTokenSupplier; +import org.springframework.vault.authentication.event.AuthenticationErrorEvent; +import org.springframework.vault.authentication.event.AuthenticationErrorListener; +import org.springframework.vault.authentication.event.AuthenticationEvent; +import org.springframework.vault.authentication.event.AuthenticationEventMulticaster; +import org.springframework.vault.authentication.event.AuthenticationListener; import org.springframework.vault.client.ClientHttpConnectorFactory; import org.springframework.vault.client.ReactiveVaultClients; import org.springframework.vault.client.ReactiveVaultEndpointProvider; @@ -70,7 +76,6 @@ public abstract class AbstractReactiveVaultConfiguration extends AbstractVaultCo /** * @return a {@link ReactiveVaultEndpointProvider} returning the value of * {@link #vaultEndpointProvider()}. - * * @see #vaultEndpoint() * @see #vaultEndpointProvider() * @since 2.3 @@ -153,7 +158,9 @@ public abstract class AbstractReactiveVaultConfiguration extends AbstractVaultCo @Bean @Override public SessionManager sessionManager() { - return new ReactiveSessionManagerAdapter(getReactiveSessionManager()); + ReactiveSessionManager rsm = getReactiveSessionManager(); + return rsm instanceof AuthenticationEventPublisher ? new ReactiveMulticastingSessionManagerAdapter(rsm) + : new ReactiveSessionManagerAdapter(rsm); } /** @@ -251,4 +258,50 @@ public abstract class AbstractReactiveVaultConfiguration extends AbstractVaultCo } + /** + * Extension to {@link ReactiveSessionManagerAdapter} that can multicast + * {@link AuthenticationEvent}s. + */ + static class ReactiveMulticastingSessionManagerAdapter extends ReactiveSessionManagerAdapter + implements AuthenticationEventMulticaster { + + private final AuthenticationEventMulticaster delegate; + + public ReactiveMulticastingSessionManagerAdapter(ReactiveSessionManager sessionManager) { + super(sessionManager); + this.delegate = (AuthenticationEventMulticaster) sessionManager; + } + + @Override + public void addAuthenticationListener(AuthenticationListener listener) { + delegate.addAuthenticationListener(listener); + } + + @Override + public void removeAuthenticationListener(AuthenticationListener listener) { + delegate.removeAuthenticationListener(listener); + } + + @Override + public void addErrorListener(AuthenticationErrorListener listener) { + delegate.addErrorListener(listener); + } + + @Override + public void removeErrorListener(AuthenticationErrorListener listener) { + delegate.removeErrorListener(listener); + } + + @Override + public void multicastEvent(AuthenticationEvent event) { + delegate.multicastEvent(event); + } + + @Override + public void multicastEvent(AuthenticationErrorEvent event) { + delegate.multicastEvent(event); + } + + } + }