JdbcOneTimeTokenService.setCleanupCron

Spring Security uses setter methods for optional member variables. Allows
for a null cleanupCron to disable the cleanup.

In a clustered environment it is likely that users do not want all nodes
to be performing a cleanup because it will cause contention on the ott
table.

Another example is if a user wants to invoke cleanUpExpiredTokens with a
different strategy all together, they might want to disable the cron job.

Issue gh-15735
This commit is contained in:
Rob Winch 2024-10-01 08:49:42 -05:00
parent 4787ac254d
commit 612b15abcc
2 changed files with 38 additions and 15 deletions

View File

@ -31,6 +31,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.PreparedStatementSetter;
@ -40,7 +41,6 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
*
@ -56,7 +56,7 @@ import org.springframework.util.StringUtils;
* @author Max Batischev
* @since 6.4
*/
public final class JdbcOneTimeTokenService implements OneTimeTokenService, DisposableBean {
public final class JdbcOneTimeTokenService implements OneTimeTokenService, DisposableBean, InitializingBean {
private final Log logger = LogFactory.getLog(getClass());
@ -70,7 +70,7 @@ public final class JdbcOneTimeTokenService implements OneTimeTokenService, Dispo
private ThreadPoolTaskScheduler taskScheduler;
private static final String DEFAULT_CLEANUP_CRON = "0 * * * * *";
private static final String DEFAULT_CLEANUP_CRON = "@hourly";
private static final String TABLE_NAME = "one_time_tokens";
@ -101,18 +101,6 @@ public final class JdbcOneTimeTokenService implements OneTimeTokenService, Dispo
+ " WHERE expires_at < ?";
// @formatter:on
/**
* Constructs a {@code JdbcOneTimeTokenService} using the provide parameters.
* @param jdbcOperations the JDBC operations
* @param cleanupCron cleanup cron expression
*/
public JdbcOneTimeTokenService(JdbcOperations jdbcOperations, String cleanupCron) {
Assert.isTrue(StringUtils.hasText(cleanupCron), "cleanupCron cannot be null orr empty");
Assert.notNull(jdbcOperations, "jdbcOperations cannot be null");
this.jdbcOperations = jdbcOperations;
this.taskScheduler = createTaskScheduler(cleanupCron);
}
/**
* Constructs a {@code JdbcOneTimeTokenService} using the provide parameters.
* @param jdbcOperations the JDBC operations
@ -123,6 +111,22 @@ public final class JdbcOneTimeTokenService implements OneTimeTokenService, Dispo
this.taskScheduler = createTaskScheduler(DEFAULT_CLEANUP_CRON);
}
/**
* Sets the chron expression used for cleaning up expired sessions. The default is to
* run hourly.
*
* For more advanced use cases the cleanupCron may be set to null which will disable
* the built-in cleanup. Users can then invoke {@link #cleanupExpiredTokens()} using
* custom logic.
* @param cleanupCron the chron expression passed to {@link CronTrigger} used for
* determining how frequent to perform cleanup. The default is "@hourly".
* @see CronTrigger
* @see #cleanupExpiredTokens()
*/
public void setCleanupCron(String cleanupCron) {
this.taskScheduler = createTaskScheduler(cleanupCron);
}
@Override
public OneTimeToken generate(GenerateOneTimeTokenRequest request) {
Assert.notNull(request, "generateOneTimeTokenRequest cannot be null");
@ -174,6 +178,9 @@ public final class JdbcOneTimeTokenService implements OneTimeTokenService, Dispo
}
private ThreadPoolTaskScheduler createTaskScheduler(String cleanupCron) {
if (cleanupCron == null) {
return null;
}
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setThreadNamePrefix("spring-one-time-tokens-");
taskScheduler.initialize();
@ -188,6 +195,11 @@ public final class JdbcOneTimeTokenService implements OneTimeTokenService, Dispo
this.logger.debug("Cleaned up " + deletedCount + " expired tokens");
}
@Override
public void afterPropertiesSet() throws Exception {
this.taskScheduler.afterPropertiesSet();
}
@Override
public void destroy() throws Exception {
if (this.taskScheduler != null) {

View File

@ -175,6 +175,17 @@ public class JdbcOneTimeTokenServiceTests {
assertThat(deletedOneTimeToken2).isNull();
}
@Test
void setCleanupChronWhenNullThenNoException() {
this.oneTimeTokenService.setCleanupCron(null);
}
@Test
void setCleanupChronWhenAlreadyNullThenNoException() {
this.oneTimeTokenService.setCleanupCron(null);
this.oneTimeTokenService.setCleanupCron(null);
}
private void saveToken(OneTimeToken oneTimeToken) {
List<SqlParameterValue> parameters = this.oneTimeTokenParametersMapper.apply(oneTimeToken);
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray());