Introduce AuthenticationEventMulticaster interface.

Closes gh-816
This commit is contained in:
Mark Paluch
2023-09-21 11:18:13 +02:00
parent 693f9cd46d
commit 8babcd387d
5 changed files with 167 additions and 45 deletions

View File

@@ -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<AuthenticationListener> 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);
}
}

View File

@@ -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));
}
};

View File

@@ -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<TokenWrapper> 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));
}
};

View File

@@ -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.
* <p>
* 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);
}

View File

@@ -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);
}
}
}