Extract common base class for LifecycleAwareSessionManager.
See gh-159.
This commit is contained in:
@@ -16,24 +16,15 @@
|
||||
package org.springframework.vault.authentication;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.Trigger;
|
||||
import org.springframework.scheduling.TriggerContext;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.vault.VaultException;
|
||||
@@ -46,41 +37,35 @@ import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
|
||||
/**
|
||||
* Lifecycle-aware Session Manager. This {@link SessionManager} obtains tokens from a
|
||||
* {@link ClientAuthentication} upon {@link #getSessionToken() request}. Tokens are
|
||||
* renewed asynchronously if a token has a lease duration. This happens 5 seconds before
|
||||
* the token expires, see {@link #REFRESH_PERIOD_BEFORE_EXPIRY}.
|
||||
* Lifecycle-aware {@link SessionManager Session Manager}. This {@link SessionManager}
|
||||
* obtains tokens from a {@link ClientAuthentication} upon {@link #getSessionToken()
|
||||
* request} synchronizing multiple threads attempting to obtain a token concurrently.
|
||||
* <p>
|
||||
* Tokens are renewed asynchronously if a token has a lease duration. This happens 5
|
||||
* seconds before the token expires, see {@link #REFRESH_PERIOD_BEFORE_EXPIRY}.
|
||||
* <p>
|
||||
* This {@link SessionManager} also implements {@link DisposableBean} to revoke the
|
||||
* {@link LoginToken} once it's not required anymore. Token revocation will stop regular
|
||||
* token refresh. Tokens are only revoked only if the associated
|
||||
* {@link ClientAuthentication} returned a {@link LoginToken}.
|
||||
* {@link ClientAuthentication} returns a {@link LoginToken}.
|
||||
* <p>
|
||||
* If Token renewal runs into a client-side error, it assumes the token was
|
||||
* revoked/expired and discards the token state so the next attempt will lead to another
|
||||
* revoked/expired. It discards the token state so the next attempt will lead to another
|
||||
* login attempt.
|
||||
* <p>
|
||||
* By default, {@link VaultToken} are looked up in Vault to determine renewability and the
|
||||
* remaining TTL, see {@link #setTokenSelfLookupEnabled(boolean)}.
|
||||
* <p>
|
||||
* This class is thread-safe.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Steven Swor
|
||||
* @see LoginToken
|
||||
* @see SessionManager
|
||||
* @see AsyncTaskExecutor
|
||||
* @see TaskScheduler
|
||||
*/
|
||||
public class LifecycleAwareSessionManager implements SessionManager, DisposableBean {
|
||||
|
||||
/**
|
||||
* Refresh 5 seconds before the token expires.
|
||||
*/
|
||||
public static final int REFRESH_PERIOD_BEFORE_EXPIRY = 5;
|
||||
|
||||
private static final RefreshTrigger DEFAULT_TRIGGER = new FixedTimeoutRefreshTrigger(
|
||||
REFRESH_PERIOD_BEFORE_EXPIRY, TimeUnit.SECONDS);
|
||||
|
||||
private static final Log logger = LogFactory
|
||||
.getLog(LifecycleAwareSessionManager.class);
|
||||
public class LifecycleAwareSessionManager extends LifecycleAwareSessionManagerSupport
|
||||
implements SessionManager, DisposableBean {
|
||||
|
||||
/**
|
||||
* Client authentication mechanism. Used to obtain a {@link VaultToken} or
|
||||
@@ -93,27 +78,8 @@ public class LifecycleAwareSessionManager implements SessionManager, DisposableB
|
||||
*/
|
||||
private final RestOperations restOperations;
|
||||
|
||||
/**
|
||||
* Threading infrastructure for token renewal/refresh.
|
||||
*/
|
||||
private final TaskScheduler taskScheduler;
|
||||
|
||||
/**
|
||||
* Trigger to calculate the next renewal time.
|
||||
*/
|
||||
private final RefreshTrigger refreshTrigger;
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
/**
|
||||
* Controls whether to perform a token self-lookup using
|
||||
* {@code auth/token/lookup-self} for {@link VaultToken}s obtained from a
|
||||
* {@link ClientAuthentication}. Self-lookup determines whether a token is renewable
|
||||
* and its TTL. Self lookup is skipped for {@link LoginToken}. Self-lookup requests
|
||||
* decrement token usage count by one. Skipped for {@link LoginToken}.
|
||||
*/
|
||||
private boolean tokenSelfLookupEnabled = true;
|
||||
|
||||
/**
|
||||
* The token state: Contains the currently valid token that identifies the Vault
|
||||
* session.
|
||||
@@ -122,21 +88,29 @@ public class LifecycleAwareSessionManager implements SessionManager, DisposableB
|
||||
|
||||
/**
|
||||
* Create a {@link LifecycleAwareSessionManager} given {@link ClientAuthentication},
|
||||
* {@link AsyncTaskExecutor} and {@link RestOperations}.
|
||||
* {@link TaskScheduler} and {@link RestOperations}.
|
||||
*
|
||||
* @param clientAuthentication must not be {@literal null}.
|
||||
* @param taskScheduler must not be {@literal null}.
|
||||
* @param restOperations must not be {@literal null}.
|
||||
* @since 1.0.1
|
||||
*/
|
||||
public LifecycleAwareSessionManager(ClientAuthentication clientAuthentication,
|
||||
TaskScheduler taskScheduler, RestOperations restOperations) {
|
||||
|
||||
this(clientAuthentication, taskScheduler, restOperations, DEFAULT_TRIGGER);
|
||||
super(taskScheduler);
|
||||
|
||||
Assert.notNull(clientAuthentication, "ClientAuthentication must not be null");
|
||||
Assert.notNull(taskScheduler, "TaskScheduler must not be null");
|
||||
Assert.notNull(restOperations, "RestOperations must not be null");
|
||||
|
||||
this.clientAuthentication = clientAuthentication;
|
||||
this.restOperations = restOperations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link LifecycleAwareSessionManager} given {@link ClientAuthentication},
|
||||
* {@link AsyncTaskExecutor} and {@link RestOperations}.
|
||||
* {@link TaskScheduler} and {@link RestOperations}.
|
||||
*
|
||||
* @param clientAuthentication must not be {@literal null}.
|
||||
* @param taskScheduler must not be {@literal null}.
|
||||
@@ -148,6 +122,8 @@ public class LifecycleAwareSessionManager implements SessionManager, DisposableB
|
||||
TaskScheduler taskScheduler, RestOperations restOperations,
|
||||
RefreshTrigger refreshTrigger) {
|
||||
|
||||
super(taskScheduler, refreshTrigger);
|
||||
|
||||
Assert.notNull(clientAuthentication, "ClientAuthentication must not be null");
|
||||
Assert.notNull(taskScheduler, "TaskScheduler must not be null");
|
||||
Assert.notNull(restOperations, "RestOperations must not be null");
|
||||
@@ -155,39 +131,6 @@ public class LifecycleAwareSessionManager implements SessionManager, DisposableB
|
||||
|
||||
this.clientAuthentication = clientAuthentication;
|
||||
this.restOperations = restOperations;
|
||||
this.taskScheduler = taskScheduler;
|
||||
this.refreshTrigger = refreshTrigger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether token self-lookup is enabled to augment {@link VaultToken} obtained
|
||||
* from a {@link ClientAuthentication}. Self-lookup determines whether a token is
|
||||
* renewable and its TTL. Self lookup is skipped for {@link LoginToken}. Self-lookup
|
||||
* requests decrement token usage count by one. Skipped for {@link LoginToken}.
|
||||
* <p/>
|
||||
* Self-lookup for tokens without a permission to access
|
||||
* {@code auth/token/lookup-self} will fail gracefully and continue without token
|
||||
* renewal.
|
||||
*
|
||||
* @return {@literal true} to enable self-lookup, {@literal false} to disable
|
||||
* self-lookup. Enabled by default.
|
||||
* @since 2.0
|
||||
*/
|
||||
public boolean isTokenSelfLookupEnabled() {
|
||||
return tokenSelfLookupEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables/disables token self-lookup. Self-lookup augments {@link VaultToken}
|
||||
* obtained from a {@link ClientAuthentication}. Self-lookup determines whether a
|
||||
* token is renewable and its TTL.
|
||||
*
|
||||
* @param tokenSelfLookupEnabled {@literal true} to enable self-lookup,
|
||||
* {@literal false} to disable self-lookup. Enabled by default.
|
||||
* @since 2.0
|
||||
*/
|
||||
public void setTokenSelfLookupEnabled(boolean tokenSelfLookupEnabled) {
|
||||
this.tokenSelfLookupEnabled = tokenSelfLookupEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -247,7 +190,8 @@ public class LifecycleAwareSessionManager implements SessionManager, DisposableB
|
||||
|
||||
LoginToken renewed = LoginTokenUtil.from(vaultResponse.getRequiredAuth());
|
||||
|
||||
Duration validTtlThreshold = refreshTrigger.getValidTtlThreshold(renewed);
|
||||
Duration validTtlThreshold = getRefreshTrigger()
|
||||
.getValidTtlThreshold(renewed);
|
||||
if (renewed.getLeaseDuration().compareTo(validTtlThreshold) <= 0) {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
@@ -348,161 +292,33 @@ public class LifecycleAwareSessionManager implements SessionManager, DisposableB
|
||||
|
||||
logger.info("Scheduling Token renewal");
|
||||
|
||||
final Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (LifecycleAwareSessionManager.this.token.isPresent()
|
||||
&& isTokenRenewable()) {
|
||||
if (renewToken()) {
|
||||
scheduleRenewal();
|
||||
}
|
||||
Runnable task = () -> {
|
||||
try {
|
||||
if (LifecycleAwareSessionManager.this.token.isPresent()
|
||||
&& isTokenRenewable()) {
|
||||
if (renewToken()) {
|
||||
scheduleRenewal();
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("Cannot renew VaultToken", e);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("Cannot renew VaultToken", e);
|
||||
}
|
||||
};
|
||||
|
||||
Optional<TokenWrapper> token = this.token;
|
||||
|
||||
token.ifPresent(tokenWrapper -> taskScheduler.schedule(task,
|
||||
token.ifPresent(tokenWrapper -> getTaskScheduler().schedule(task,
|
||||
createTrigger(tokenWrapper)));
|
||||
}
|
||||
|
||||
private OneShotTrigger createTrigger(TokenWrapper tokenWrapper) {
|
||||
|
||||
return new OneShotTrigger(
|
||||
refreshTrigger.nextExecutionTime((LoginToken) tokenWrapper.getToken()));
|
||||
getRefreshTrigger().nextExecutionTime(
|
||||
(LoginToken) tokenWrapper.getToken()));
|
||||
}
|
||||
|
||||
/**
|
||||
* This one-shot trigger creates only one execution time to trigger an execution only
|
||||
* once.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
private static class OneShotTrigger implements Trigger {
|
||||
|
||||
private final AtomicBoolean fired = new AtomicBoolean();
|
||||
|
||||
private final Date nextExecutionTime;
|
||||
|
||||
@Nullable
|
||||
public Date nextExecutionTime(TriggerContext triggerContext) {
|
||||
|
||||
if (fired.compareAndSet(false, true)) {
|
||||
return nextExecutionTime;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common interface for trigger objects that determine the next execution time of a
|
||||
* refresh task.
|
||||
*/
|
||||
public interface RefreshTrigger {
|
||||
|
||||
/**
|
||||
* Determine the next execution time according to the given trigger context.
|
||||
*
|
||||
* @param loginToken login token encapsulating renewability and lease duration.
|
||||
* @return the next execution time as defined by the trigger, or {@code null} if
|
||||
* the trigger won't fire anymore
|
||||
*/
|
||||
Date nextExecutionTime(LoginToken loginToken);
|
||||
|
||||
/**
|
||||
* Returns the minimum TTL duration to consider a token valid after renewal.
|
||||
* Tokens with a shorter TTL are revoked and considered expired.
|
||||
*
|
||||
* @param loginToken the login token after renewal.
|
||||
* @return minimum TTL {@link Duration} to consider a token valid.
|
||||
* @since 2.0
|
||||
*/
|
||||
Duration getValidTtlThreshold(LoginToken loginToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link RefreshTrigger} implementation using a fixed timeout to schedule renewal
|
||||
* before a {@link LoginToken} expires.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.0.1
|
||||
*/
|
||||
public static class FixedTimeoutRefreshTrigger implements RefreshTrigger {
|
||||
|
||||
private static final Duration ONE_SECOND = Duration.ofSeconds(1);
|
||||
|
||||
private final Duration duration;
|
||||
private final Duration validTtlThreshold;
|
||||
|
||||
/**
|
||||
* Create a new {@link FixedTimeoutRefreshTrigger} to calculate execution times of
|
||||
* {@code timeout} before the {@link LoginToken} expires
|
||||
*
|
||||
* @param timeout timeout value, non-negative long value.
|
||||
* @param timeUnit must not be {@literal null}.
|
||||
*/
|
||||
public FixedTimeoutRefreshTrigger(long timeout, TimeUnit timeUnit) {
|
||||
|
||||
Assert.isTrue(timeout >= 0,
|
||||
"Timeout duration must be greater or equal to zero");
|
||||
Assert.notNull(timeUnit, "TimeUnit must not be null");
|
||||
|
||||
this.duration = Duration.ofMillis(timeUnit.toMillis(timeout));
|
||||
this.validTtlThreshold = Duration.ofMillis(timeUnit.toMillis(timeout) + 2000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link FixedTimeoutRefreshTrigger} to calculate execution times of
|
||||
* {@code timeout} before the {@link LoginToken} expires. Valid TTL threshold is
|
||||
* set to two seconds longer to compensate for timing issues during scheduling.
|
||||
*
|
||||
* @param timeout timeout value.
|
||||
* @since 2.0
|
||||
*/
|
||||
public FixedTimeoutRefreshTrigger(Duration timeout) {
|
||||
this(timeout, timeout.plus(Duration.ofSeconds(2)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link FixedTimeoutRefreshTrigger} to calculate execution times of
|
||||
* {@code timeout} before the {@link LoginToken} expires.
|
||||
*
|
||||
* @param timeout timeout value.
|
||||
* @param validTtlThreshold minimum TTL duration to consider a Token as valid.
|
||||
* Tokens with a shorter TTL are not used anymore. Should be greater than
|
||||
* {@code timeout} to prevent token expiry.
|
||||
* @since 2.0
|
||||
*/
|
||||
public FixedTimeoutRefreshTrigger(Duration timeout, Duration validTtlThreshold) {
|
||||
|
||||
Assert.isTrue(timeout.toMillis() >= 0,
|
||||
"Timeout duration must be greater or equal to zero");
|
||||
|
||||
Assert.notNull(validTtlThreshold, "Valid TTL threshold must not be null");
|
||||
|
||||
this.duration = timeout;
|
||||
this.validTtlThreshold = validTtlThreshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date nextExecutionTime(LoginToken loginToken) {
|
||||
|
||||
long milliseconds = Math.max(ONE_SECOND.toMillis(), loginToken
|
||||
.getLeaseDuration().toMillis() - duration.toMillis());
|
||||
|
||||
return new Date(System.currentTimeMillis() + milliseconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getValidTtlThreshold(LoginToken loginToken) {
|
||||
return validTtlThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a {@link VaultToken} and specifies whether the token is revocable on factory
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* Copyright 2018 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
|
||||
*
|
||||
* http://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;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.Trigger;
|
||||
import org.springframework.scheduling.TriggerContext;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
|
||||
/**
|
||||
* Support class to build Lifecycle-aware Session Manager implementations, defining common
|
||||
* properties such as the {@link TaskScheduler} and {@link RefreshTrigger}. Typically used
|
||||
* within the framework itself.
|
||||
* <p>
|
||||
* Not intended to be used directly.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public abstract class LifecycleAwareSessionManagerSupport {
|
||||
|
||||
/**
|
||||
* Refresh 5 seconds before the token expires.
|
||||
*/
|
||||
public static final int REFRESH_PERIOD_BEFORE_EXPIRY = 5;
|
||||
|
||||
private static final RefreshTrigger DEFAULT_TRIGGER = new FixedTimeoutRefreshTrigger(
|
||||
REFRESH_PERIOD_BEFORE_EXPIRY, TimeUnit.SECONDS);
|
||||
|
||||
/**
|
||||
* Logger available to subclasses.
|
||||
*/
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
/**
|
||||
* Threading infrastructure for token renewal/refresh.
|
||||
*/
|
||||
private final TaskScheduler taskScheduler;
|
||||
|
||||
/**
|
||||
* Trigger to calculate the next renewal time.
|
||||
*/
|
||||
private final RefreshTrigger refreshTrigger;
|
||||
|
||||
/**
|
||||
* Controls whether to perform a token self-lookup using
|
||||
* {@code auth/token/lookup-self} for {@link VaultToken}s obtained from a
|
||||
* {@link ClientAuthentication}. Self-lookup determines whether a token is renewable
|
||||
* and its TTL. Self lookup is skipped for {@link LoginToken}. Self-lookup requests
|
||||
* decrement token usage count by one. Skipped for {@link LoginToken}.
|
||||
*/
|
||||
private boolean tokenSelfLookupEnabled = true;
|
||||
|
||||
/**
|
||||
* Create a {@link LifecycleAwareSessionManager} given {@link TaskScheduler}. Using
|
||||
* {@link #DEFAULT_TRIGGER} to trigger refresh.
|
||||
*
|
||||
* @param taskScheduler must not be {@literal null}.
|
||||
*/
|
||||
public LifecycleAwareSessionManagerSupport(TaskScheduler taskScheduler) {
|
||||
this(taskScheduler, DEFAULT_TRIGGER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link LifecycleAwareSessionManager} given {@link TaskScheduler} and
|
||||
* {@link RefreshTrigger}.
|
||||
*
|
||||
* @param taskScheduler must not be {@literal null}.
|
||||
* @param refreshTrigger must not be {@literal null}.
|
||||
*/
|
||||
public LifecycleAwareSessionManagerSupport(TaskScheduler taskScheduler,
|
||||
RefreshTrigger refreshTrigger) {
|
||||
|
||||
Assert.notNull(taskScheduler, "TaskScheduler must not be null");
|
||||
Assert.notNull(refreshTrigger, "RefreshTrigger must not be null");
|
||||
|
||||
this.taskScheduler = taskScheduler;
|
||||
this.refreshTrigger = refreshTrigger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether token self-lookup is enabled to augment {@link VaultToken} obtained
|
||||
* from a {@link ClientAuthentication}. Self-lookup determines whether a token is
|
||||
* renewable and its TTL. Self lookup is skipped for {@link LoginToken}. Self-lookup
|
||||
* requests decrement token usage count by one. Skipped for {@link LoginToken}.
|
||||
* <p>
|
||||
* Self-lookup for tokens without a permission to access
|
||||
* {@code auth/token/lookup-self} will fail gracefully and continue without token
|
||||
* renewal.
|
||||
*
|
||||
* @return {@literal true} to enable self-lookup, {@literal false} to disable
|
||||
* self-lookup. Enabled by default.
|
||||
*/
|
||||
protected boolean isTokenSelfLookupEnabled() {
|
||||
return tokenSelfLookupEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables/disables token self-lookup. Self-lookup augments {@link VaultToken}
|
||||
* obtained from a {@link ClientAuthentication}. Self-lookup determines whether a
|
||||
* token is renewable and its TTL.
|
||||
*
|
||||
* @param tokenSelfLookupEnabled {@literal true} to enable self-lookup,
|
||||
* {@literal false} to disable self-lookup. Enabled by default.
|
||||
*/
|
||||
public void setTokenSelfLookupEnabled(boolean tokenSelfLookupEnabled) {
|
||||
this.tokenSelfLookupEnabled = tokenSelfLookupEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the underlying {@link TaskScheduler}.
|
||||
*/
|
||||
protected TaskScheduler getTaskScheduler() {
|
||||
return taskScheduler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the underlying {@link RefreshTrigger}.
|
||||
*/
|
||||
protected RefreshTrigger getRefreshTrigger() {
|
||||
return refreshTrigger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the Token falls below its
|
||||
* {@link RefreshTrigger#getValidTtlThreshold(LoginToken) validity threshold}.
|
||||
* Typically used to discard a token.
|
||||
*
|
||||
* @param loginToken must not be {@literal null}.
|
||||
* @return {@literal true} if token validity falls below validity threshold,
|
||||
* {@literal false} if still valid.
|
||||
*/
|
||||
protected boolean isExpired(LoginToken loginToken) {
|
||||
|
||||
Duration validTtlThreshold = getRefreshTrigger().getValidTtlThreshold(loginToken);
|
||||
return loginToken.getLeaseDuration().compareTo(validTtlThreshold) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This one-shot trigger creates only one execution time to trigger an execution only
|
||||
* once.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
static class OneShotTrigger implements Trigger {
|
||||
|
||||
private final AtomicBoolean fired = new AtomicBoolean();
|
||||
|
||||
private final Date nextExecutionTime;
|
||||
|
||||
@Nullable
|
||||
public Date nextExecutionTime(TriggerContext triggerContext) {
|
||||
|
||||
if (fired.compareAndSet(false, true)) {
|
||||
return nextExecutionTime;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common interface for trigger objects that determine the next execution time of a
|
||||
* refresh task.
|
||||
*/
|
||||
public interface RefreshTrigger {
|
||||
|
||||
/**
|
||||
* Determine the next execution time according to the given trigger context.
|
||||
*
|
||||
* @param loginToken login token encapsulating renewability and lease duration.
|
||||
* @return the next execution time as defined by the trigger, or {@code null} if
|
||||
* the trigger won't fire anymore
|
||||
*/
|
||||
Date nextExecutionTime(LoginToken loginToken);
|
||||
|
||||
/**
|
||||
* Returns the minimum TTL duration to consider a token valid after renewal.
|
||||
* Tokens with a shorter TTL are revoked and considered expired.
|
||||
*
|
||||
* @param loginToken the login token after renewal.
|
||||
* @return minimum TTL {@link Duration} to consider a token valid.
|
||||
* @since 2.0
|
||||
*/
|
||||
Duration getValidTtlThreshold(LoginToken loginToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link RefreshTrigger} implementation using a fixed timeout to schedule renewal
|
||||
* before a {@link LoginToken} expires.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.0.1
|
||||
*/
|
||||
public static class FixedTimeoutRefreshTrigger implements RefreshTrigger {
|
||||
|
||||
private static final Duration ONE_SECOND = Duration.ofSeconds(1);
|
||||
|
||||
private final Duration duration;
|
||||
private final Duration validTtlThreshold;
|
||||
|
||||
/**
|
||||
* Create a new {@link FixedTimeoutRefreshTrigger} to calculate execution times of
|
||||
* {@code timeout} before the {@link LoginToken} expires
|
||||
*
|
||||
* @param timeout timeout value, non-negative long value.
|
||||
* @param timeUnit must not be {@literal null}.
|
||||
*/
|
||||
public FixedTimeoutRefreshTrigger(long timeout, TimeUnit timeUnit) {
|
||||
|
||||
Assert.isTrue(timeout >= 0,
|
||||
"Timeout duration must be greater or equal to zero");
|
||||
Assert.notNull(timeUnit, "TimeUnit must not be null");
|
||||
|
||||
this.duration = Duration.ofMillis(timeUnit.toMillis(timeout));
|
||||
this.validTtlThreshold = Duration.ofMillis(timeUnit.toMillis(timeout) + 2000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link FixedTimeoutRefreshTrigger} to calculate execution times of
|
||||
* {@code timeout} before the {@link LoginToken} expires. Valid TTL threshold is
|
||||
* set to two seconds longer to compensate for timing issues during scheduling.
|
||||
*
|
||||
* @param timeout timeout value.
|
||||
* @since 2.0
|
||||
*/
|
||||
public FixedTimeoutRefreshTrigger(Duration timeout) {
|
||||
this(timeout, timeout.plus(Duration.ofSeconds(2)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link FixedTimeoutRefreshTrigger} to calculate execution times of
|
||||
* {@code timeout} before the {@link LoginToken} expires.
|
||||
*
|
||||
* @param timeout timeout value.
|
||||
* @param validTtlThreshold minimum TTL duration to consider a Token as valid.
|
||||
* Tokens with a shorter TTL are not used anymore. Should be greater than
|
||||
* {@code timeout} to prevent token expiry.
|
||||
* @since 2.0
|
||||
*/
|
||||
public FixedTimeoutRefreshTrigger(Duration timeout, Duration validTtlThreshold) {
|
||||
|
||||
Assert.isTrue(timeout.toMillis() >= 0,
|
||||
"Timeout duration must be greater or equal to zero");
|
||||
|
||||
Assert.notNull(validTtlThreshold, "Valid TTL threshold must not be null");
|
||||
|
||||
this.duration = timeout;
|
||||
this.validTtlThreshold = validTtlThreshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date nextExecutionTime(LoginToken loginToken) {
|
||||
|
||||
long milliseconds = Math.max(ONE_SECOND.toMillis(), loginToken
|
||||
.getLeaseDuration().toMillis() - duration.toMillis());
|
||||
|
||||
return new Date(System.currentTimeMillis() + milliseconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getValidTtlThreshold(LoginToken loginToken) {
|
||||
return validTtlThreshold;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2018 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
|
||||
*
|
||||
* http://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;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.vault.authentication.LifecycleAwareSessionManagerSupport.FixedTimeoutRefreshTrigger;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link LifecycleAwareSessionManagerSupport} .
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class LifecycleAwareSessionManagerSupportUnitTests {
|
||||
|
||||
@Test
|
||||
public void shouldScheduleNextExecutionTimeCorrectly() {
|
||||
|
||||
FixedTimeoutRefreshTrigger trigger = new FixedTimeoutRefreshTrigger(5,
|
||||
TimeUnit.SECONDS);
|
||||
|
||||
Date nextExecutionTime = trigger.nextExecutionTime(LoginToken.of(
|
||||
"foo".toCharArray(), Duration.ofMinutes(1)));
|
||||
assertThat(nextExecutionTime).isBetween(
|
||||
new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(52)),
|
||||
new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(56)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldScheduleNextExecutionIfValidityLessThanTimeout() {
|
||||
|
||||
FixedTimeoutRefreshTrigger trigger = new FixedTimeoutRefreshTrigger(5,
|
||||
TimeUnit.SECONDS);
|
||||
|
||||
Date nextExecutionTime = trigger.nextExecutionTime(LoginToken.of(
|
||||
"foo".toCharArray(), Duration.ofSeconds(2)));
|
||||
assertThat(nextExecutionTime).isBetween(
|
||||
new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(0)),
|
||||
new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(2)));
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,8 @@ package org.springframework.vault.authentication;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -36,7 +34,6 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.Trigger;
|
||||
import org.springframework.vault.authentication.LifecycleAwareSessionManager.FixedTimeoutRefreshTrigger;
|
||||
import org.springframework.vault.client.VaultHttpHeaders;
|
||||
import org.springframework.vault.support.VaultResponse;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
@@ -333,32 +330,6 @@ public class LifecycleAwareSessionManagerUnitTests {
|
||||
verify(clientAuthentication, times(1)).login();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldScheduleNextExecutionTimeCorrectly() {
|
||||
|
||||
FixedTimeoutRefreshTrigger trigger = new FixedTimeoutRefreshTrigger(5,
|
||||
TimeUnit.SECONDS);
|
||||
|
||||
Date nextExecutionTime = trigger.nextExecutionTime(LoginToken.of(
|
||||
"foo".toCharArray(), Duration.ofMinutes(1)));
|
||||
assertThat(nextExecutionTime).isBetween(
|
||||
new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(52)),
|
||||
new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(56)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldScheduleNextExecutionIfValidityLessThanTimeout() {
|
||||
|
||||
FixedTimeoutRefreshTrigger trigger = new FixedTimeoutRefreshTrigger(5,
|
||||
TimeUnit.SECONDS);
|
||||
|
||||
Date nextExecutionTime = trigger.nextExecutionTime(LoginToken.of(
|
||||
"foo".toCharArray(), Duration.ofSeconds(2)));
|
||||
assertThat(nextExecutionTime).isBetween(
|
||||
new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(0)),
|
||||
new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(2)));
|
||||
}
|
||||
|
||||
private static VaultResponse fromToken(LoginToken loginToken) {
|
||||
|
||||
Map<String, Object> auth = new HashMap<>();
|
||||
|
||||
Reference in New Issue
Block a user