diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo.Abp.IdentityServer.Domain.csproj b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo.Abp.IdentityServer.Domain.csproj
index 70d0fbcba7..e7f4a6bc43 100644
--- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo.Abp.IdentityServer.Domain.csproj
+++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo.Abp.IdentityServer.Domain.csproj
@@ -20,6 +20,7 @@
+
diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Devices/DeviceFlowStore.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Devices/DeviceFlowStore.cs
index 771e3acbde..4bd55ab2c6 100644
--- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Devices/DeviceFlowStore.cs
+++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Devices/DeviceFlowStore.cs
@@ -1,5 +1,4 @@
using System;
-using System.IdentityModel.Tokens.Jwt;
using System.Threading.Tasks;
using IdentityModel;
using IdentityServer4.Models;
diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Devices/IDeviceFlowCodesRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Devices/IDeviceFlowCodesRepository.cs
index 41c7e92574..b6bcb69bed 100644
--- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Devices/IDeviceFlowCodesRepository.cs
+++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Devices/IDeviceFlowCodesRepository.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
@@ -16,5 +17,11 @@ namespace Volo.Abp.IdentityServer.Devices
string deviceCode,
CancellationToken cancellationToken = default
);
+
+ Task> GetListByExpirationAsync(
+ DateTime maxExpirationDate,
+ int maxResultCount,
+ CancellationToken cancellationToken = default
+ );
}
}
diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/IPersistentGrantRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/IPersistentGrantRepository.cs
index 6e316806b6..65cfabd7e7 100644
--- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/IPersistentGrantRepository.cs
+++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/IPersistentGrantRepository.cs
@@ -18,6 +18,12 @@ namespace Volo.Abp.IdentityServer.Grants
CancellationToken cancellationToken = default
);
+ Task> GetListByExpirationAsync(
+ DateTime maxExpirationDate,
+ int maxResultCount,
+ CancellationToken cancellationToken = default
+ );
+
Task DeleteAsync(
string subjectId,
string clientId,
diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupBackgroundWorker.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupBackgroundWorker.cs
new file mode 100644
index 0000000000..4bec8318eb
--- /dev/null
+++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupBackgroundWorker.cs
@@ -0,0 +1,34 @@
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Volo.Abp.BackgroundWorkers;
+using Volo.Abp.Threading;
+
+namespace Volo.Abp.IdentityServer.Tokens
+{
+ public class TokenCleanupBackgroundWorker : AsyncPeriodicBackgroundWorkerBase
+ {
+ protected TokenCleanupOptions Options { get; }
+
+ public TokenCleanupBackgroundWorker(
+ AbpTimer timer,
+ IServiceScopeFactory serviceScopeFactory,
+ IOptions options)
+ : base(
+ timer,
+ serviceScopeFactory)
+ {
+ Options = options.Value;
+ timer.Period = Options.CleanupPeriod;
+ }
+
+ protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext)
+ {
+ await workerContext
+ .ServiceProvider
+ .GetRequiredService()
+ .CleanAsync()
+ .ConfigureAwait(false);
+ }
+ }
+}
diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupOptions.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupOptions.cs
new file mode 100644
index 0000000000..3252008506
--- /dev/null
+++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupOptions.cs
@@ -0,0 +1,34 @@
+using Volo.Abp.BackgroundWorkers;
+
+namespace Volo.Abp.IdentityServer.Tokens
+{
+ public class TokenCleanupOptions
+ {
+ ///
+ /// Default: 3,600,000 ms.
+ ///
+ public int CleanupPeriod { get; set; } = 3_600_000;
+
+ ///
+ /// Default value: 100.
+ ///
+ public int CleanupBatchSize { get; set; } = 100;
+
+ ///
+ /// The number of loop if there are
+ /// more than tokens in the database.
+ /// So, if is 10 and is 100,
+ /// then the cleanup worker will clean 1,000 items in one at max.
+ ///
+ /// Default value: 10.
+ ///
+ public int CleanupLoopCount { get; set; } = 10;
+
+ ///
+ /// Default value: true.
+ /// If is false,
+ /// this property is ignored and the cleanup worker doesn't work for this application instance.
+ ///
+ public bool EnableCleanup { get; set; } = true;
+ }
+}
\ No newline at end of file
diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupService.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupService.cs
new file mode 100644
index 0000000000..ac38f792c1
--- /dev/null
+++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupService.cs
@@ -0,0 +1,88 @@
+using System.Threading.Tasks;
+using Microsoft.Extensions.Options;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.IdentityServer.Devices;
+using Volo.Abp.IdentityServer.Grants;
+using Volo.Abp.Timing;
+using Volo.Abp.Uow;
+
+namespace Volo.Abp.IdentityServer.Tokens
+{
+ public class TokenCleanupService : ITransientDependency
+ {
+ protected IPersistentGrantRepository PersistentGrantRepository { get; }
+ protected IDeviceFlowCodesRepository DeviceFlowCodesRepository { get; }
+ protected IClock Clock { get; }
+ protected TokenCleanupOptions Options { get; }
+
+ public TokenCleanupService(
+ IPersistentGrantRepository persistentGrantRepository,
+ IDeviceFlowCodesRepository deviceFlowCodesRepository,
+ IClock clock,
+ IOptions options)
+ {
+ PersistentGrantRepository = persistentGrantRepository;
+ DeviceFlowCodesRepository = deviceFlowCodesRepository;
+ Clock = clock;
+ Options = options.Value;
+ }
+
+ public virtual async Task CleanAsync()
+ {
+ await RemoveGrantsAsync()
+ .ConfigureAwait(false);
+
+ await RemoveDeviceCodesAsync()
+ .ConfigureAwait(false);
+ }
+
+ [UnitOfWork]
+ protected virtual async Task RemoveGrantsAsync()
+ {
+ for (int i = 0; i < Options.CleanupLoopCount; i++)
+ {
+ var persistentGrants = await PersistentGrantRepository
+ .GetListByExpirationAsync(Clock.Now, Options.CleanupBatchSize)
+ .ConfigureAwait(false);
+
+ //TODO: Can be optimized if the repository implements the batch deletion
+ foreach (var persistentGrant in persistentGrants)
+ {
+ await PersistentGrantRepository
+ .DeleteAsync(persistentGrant)
+ .ConfigureAwait(false);
+ }
+
+ //No need to continue to query if it gets more than max items.
+ if (persistentGrants.Count < Options.CleanupBatchSize)
+ {
+ break;
+ }
+ }
+ }
+
+ protected virtual async Task RemoveDeviceCodesAsync()
+ {
+ for (int i = 0; i < Options.CleanupLoopCount; i++)
+ {
+ var deviceFlowCodeses = await DeviceFlowCodesRepository
+ .GetListByExpirationAsync(Clock.Now, Options.CleanupBatchSize)
+ .ConfigureAwait(false);
+
+ //TODO: Can be optimized if the repository implements the batch deletion
+ foreach (var deviceFlowCodes in deviceFlowCodeses)
+ {
+ await DeviceFlowCodesRepository
+ .DeleteAsync(deviceFlowCodes)
+ .ConfigureAwait(false);
+ }
+
+ //No need to continue to query if it gets more than max items.
+ if (deviceFlowCodeses.Count < Options.CleanupBatchSize)
+ {
+ break;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Devices/DeviceFlowCodesRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Devices/DeviceFlowCodesRepository.cs
index d5ac9ae815..a8d2bce3ce 100644
--- a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Devices/DeviceFlowCodesRepository.cs
+++ b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Devices/DeviceFlowCodesRepository.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
@@ -34,5 +36,16 @@ namespace Volo.Abp.IdentityServer.Devices
.FirstOrDefaultAsync(d => d.DeviceCode == deviceCode, GetCancellationToken(cancellationToken))
.ConfigureAwait(false);
}
+
+ public async Task> GetListByExpirationAsync(DateTime maxExpirationDate, int maxResultCount,
+ CancellationToken cancellationToken = default)
+ {
+ return await DbSet
+ .Where(x => x.Expiration != null && x.Expiration < maxExpirationDate)
+ .OrderBy(x => x.ClientId)
+ .Take(maxResultCount)
+ .ToListAsync(GetCancellationToken(cancellationToken))
+ .ConfigureAwait(false);
+ }
}
}
diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Grants/PersistedGrantRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Grants/PersistedGrantRepository.cs
index ca404c84e5..cf208c7875 100644
--- a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Grants/PersistedGrantRepository.cs
+++ b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Grants/PersistedGrantRepository.cs
@@ -18,21 +18,36 @@ namespace Volo.Abp.IdentityServer.Grants
}
- public Task FindByKeyAsync(
+ public async Task FindByKeyAsync(
string key,
CancellationToken cancellationToken = default)
{
- return DbSet
- .FirstOrDefaultAsync(x => x.Key == key, GetCancellationToken(cancellationToken));
+ return await DbSet
+ .FirstOrDefaultAsync(x => x.Key == key, GetCancellationToken(cancellationToken))
+ .ConfigureAwait(false);
}
- public Task> GetListBySubjectIdAsync(
+ public async Task> GetListBySubjectIdAsync(
string subjectId,
CancellationToken cancellationToken = default)
{
- return DbSet
+ return await DbSet
.Where(x => x.SubjectId == subjectId)
- .ToListAsync(GetCancellationToken(cancellationToken));
+ .ToListAsync(GetCancellationToken(cancellationToken))
+ .ConfigureAwait(false);
+ }
+
+ public async Task> GetListByExpirationAsync(
+ DateTime maxExpirationDate,
+ int maxResultCount,
+ CancellationToken cancellationToken = default)
+ {
+ return await DbSet
+ .Where(x => x.Expiration != null && x.Expiration < maxExpirationDate)
+ .OrderBy(x => x.ClientId)
+ .Take(maxResultCount)
+ .ToListAsync(GetCancellationToken(cancellationToken))
+ .ConfigureAwait(false);
}
public async Task DeleteAsync(
diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoDeviceFlowCodesRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoDeviceFlowCodesRepository.cs
index 3ad3ecf104..86daac80a9 100644
--- a/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoDeviceFlowCodesRepository.cs
+++ b/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoDeviceFlowCodesRepository.cs
@@ -1,6 +1,8 @@
using System;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Volo.Abp.Domain.Repositories.MongoDB;
using Volo.Abp.IdentityServer.Devices;
@@ -32,5 +34,18 @@ namespace Volo.Abp.IdentityServer.MongoDB
.FirstOrDefaultAsync(d => d.DeviceCode == deviceCode, GetCancellationToken(cancellationToken))
.ConfigureAwait(false);
}
+
+ public async Task> GetListByExpirationAsync(
+ DateTime maxExpirationDate,
+ int maxResultCount,
+ CancellationToken cancellationToken = default)
+ {
+ return await GetMongoQueryable()
+ .Where(x => x.Expiration != null && x.Expiration < maxExpirationDate)
+ .OrderBy(x => x.ClientId)
+ .Take(maxResultCount)
+ .ToListAsync(GetCancellationToken(cancellationToken))
+ .ConfigureAwait(false);
+ }
}
}
\ No newline at end of file
diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoPersistedGrantRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoPersistedGrantRepository.cs
index 3ee17df174..4b414482db 100644
--- a/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoPersistedGrantRepository.cs
+++ b/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoPersistedGrantRepository.cs
@@ -27,7 +27,19 @@ namespace Volo.Abp.IdentityServer.MongoDB
{
return await GetMongoQueryable()
.Where(x => x.SubjectId == subjectId)
- .ToListAsync(GetCancellationToken(cancellationToken)).ConfigureAwait(false);
+ .ToListAsync(GetCancellationToken(cancellationToken))
+ .ConfigureAwait(false);
+ }
+
+ public async Task> GetListByExpirationAsync(DateTime maxExpirationDate, int maxResultCount,
+ CancellationToken cancellationToken = default)
+ {
+ return await GetMongoQueryable()
+ .Where(x => x.Expiration != null && x.Expiration < maxExpirationDate)
+ .OrderBy(x => x.ClientId)
+ .Take(maxResultCount)
+ .ToListAsync(GetCancellationToken(cancellationToken))
+ .ConfigureAwait(false);
}
public async Task DeleteAsync(string subjectId, string clientId, CancellationToken cancellationToken = default)