identity: use Quartz.NET for token cleanup

This commit is contained in:
Abdullah Atta
2023-01-24 15:34:18 +05:00
parent f38e61d58f
commit c560f2ac5f
5 changed files with 197 additions and 11 deletions

View File

@@ -0,0 +1,20 @@
using System.Threading.Tasks;
using Quartz;
using Streetwriters.Identity.Services;
namespace Streetwriters.Identity.Jobs
{
public class TokenCleanupJob : IJob
{
private TokenCleanup TokenCleanup { get; set; }
public TokenCleanupJob(TokenCleanup tokenCleanup)
{
TokenCleanup = tokenCleanup;
}
public Task Execute(IJobExecutionContext context)
{
return TokenCleanup.RemoveExpiredGrantsAsync();
}
}
}

View File

@@ -0,0 +1,78 @@
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using IdentityServer4.MongoDB;
using IdentityServer4.MongoDB.Configuration;
using IdentityServer4.MongoDB.DbContexts;
using IdentityServer4.MongoDB.Entities;
using IdentityServer4.MongoDB.Interfaces;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
namespace Streetwriters.Identity.Services
{
public class CustomPersistedGrantDbContext : MongoDBContextBase, IPersistedGrantDbContext
{
private readonly IMongoCollection<PersistedGrant> _persistedGrants;
public CustomPersistedGrantDbContext(IOptions<MongoDBConfiguration> settings)
: base(settings)
{
_persistedGrants = Database.GetCollection<PersistedGrant>(Constants.TableNames.PersistedGrant);
CreateIndexes();
}
private void CreateIndexes()
{
var indexOptions = new CreateIndexOptions() { Background = true };
var builder = Builders<PersistedGrant>.IndexKeys;
var expirationIndexModel = new CreateIndexModel<PersistedGrant>(builder.Descending(_ => _.Expiration), indexOptions);
var keyIndexModel = new CreateIndexModel<PersistedGrant>(builder.Ascending(_ => _.Key), indexOptions);
var subIndexModel = new CreateIndexModel<PersistedGrant>(builder.Ascending(_ => _.SubjectId), indexOptions);
var clientIdSubIndexModel = new CreateIndexModel<PersistedGrant>(
builder.Combine(
builder.Ascending(_ => _.ClientId),
builder.Ascending(_ => _.SubjectId)),
indexOptions);
var clientIdSubTypeIndexModel = new CreateIndexModel<PersistedGrant>(
builder.Combine(
builder.Ascending(_ => _.ClientId),
builder.Ascending(_ => _.SubjectId),
builder.Ascending(_ => _.Type)),
indexOptions);
_persistedGrants.Indexes.CreateOne(expirationIndexModel);
_persistedGrants.Indexes.CreateOne(keyIndexModel);
_persistedGrants.Indexes.CreateOne(subIndexModel);
_persistedGrants.Indexes.CreateOne(clientIdSubIndexModel);
_persistedGrants.Indexes.CreateOne(clientIdSubTypeIndexModel);
}
public IQueryable<PersistedGrant> PersistedGrants
{
get { return _persistedGrants.AsQueryable(); }
}
public Task Remove(Expression<Func<PersistedGrant, bool>> filter)
{
return _persistedGrants.DeleteManyAsync(filter);
}
public Task RemoveExpired()
{
return Remove(x => x.Expiration < DateTime.UtcNow.AddHours(12));
}
public Task InsertOrUpdate(Expression<Func<PersistedGrant, bool>> filter, PersistedGrant entity)
{
return _persistedGrants.ReplaceOneAsync(filter, entity, new ReplaceOptions() { IsUpsert = true });
}
}
}

View File

@@ -0,0 +1,51 @@
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using IdentityServer4.MongoDB.Interfaces;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Streetwriters.Identity.Services
{
public class TokenCleanup
{
private readonly IPersistedGrantDbContext _persistedGrantDbContext;
private readonly ILogger<TokenCleanup> _logger;
public TokenCleanup(IPersistedGrantDbContext persistedGrantDbContext, ILogger<TokenCleanup> logger)
{
_persistedGrantDbContext = persistedGrantDbContext;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// Method to clear expired persisted grants.
/// </summary>
/// <returns></returns>
public async Task RemoveExpiredGrantsAsync()
{
try
{
_logger.LogTrace("Querying for expired grants to remove");
await RemoveGrantsAsync();
//TODO: await RemoveDeviceCodesAsync();
}
catch (Exception ex)
{
_logger.LogError("Exception removing expired grants: {exception}", ex.Message);
}
}
/// <summary>
/// Removes the stale persisted grants.
/// </summary>
/// <returns></returns>
protected virtual async Task RemoveGrantsAsync()
{
await _persistedGrantDbContext.RemoveExpired();
}
}
}

View File

@@ -20,8 +20,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.IO;
using AspNetCore.Identity.Mongo;
using IdentityServer4.MongoDB.Entities;
using IdentityServer4.MongoDB.Interfaces;
using IdentityServer4.MongoDB.Options;
using IdentityServer4.MongoDB.Stores;
using IdentityServer4.ResponseHandling;
using IdentityServer4.Services;
using IdentityServer4.Stores;
using IdentityServer4.Validation;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@@ -32,12 +37,15 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Tokens;
using MongoDB.Bson.Serialization;
using Quartz;
using Streetwriters.Common;
using Streetwriters.Common.Extensions;
using Streetwriters.Common.Messages;
using Streetwriters.Common.Models;
using Streetwriters.Identity.Helpers;
using Streetwriters.Identity.Interfaces;
using Streetwriters.Identity.Jobs;
using Streetwriters.Identity.Models;
using Streetwriters.Identity.Services;
using Streetwriters.Identity.Validation;
@@ -96,7 +104,7 @@ namespace Streetwriters.Identity
options.ConnectionString = connectionString;
}).AddDefaultTokenProviders();
var builder = services.AddIdentityServer(
services.AddIdentityServer(
options =>
{
options.Events.RaiseSuccessEvents = true;
@@ -107,16 +115,6 @@ namespace Streetwriters.Identity
.AddExtensionGrantValidator<EmailGrantValidator>()
.AddExtensionGrantValidator<MFAGrantValidator>()
.AddExtensionGrantValidator<MFAPasswordGrantValidator>()
.AddOperationalStore(options =>
{
options.ConnectionString = connectionString;
}, (options) =>
{
#if !DEBUG
options.Enable = true;
options.Interval = 3600;
#endif
})
.AddConfigurationStore(options =>
{
options.ConnectionString = connectionString;
@@ -160,6 +158,15 @@ namespace Streetwriters.Identity
};
});
services.AddQuartzHostedService(q =>
{
q.WaitForJobsToComplete = true;
q.AwaitApplicationStarted = true;
q.StartDelay = TimeSpan.FromMinutes(1);
});
AddOperationalStore(services, new TokenCleanupOptions { Enable = true, Interval = 3600 * 12 });
services.AddTransient<IMFAService, MFAService>();
services.AddControllers();
services.AddTransient<IIntrospectionResponseGenerator, CustomIntrospectionResponseGenerator>();
@@ -222,5 +229,33 @@ namespace Streetwriters.Identity
endpoints.MapHealthChecks("/health");
});
}
private void AddOperationalStore(IServiceCollection services, TokenCleanupOptions tokenCleanUpOptions = null)
{
BsonClassMap.RegisterClassMap<PersistedGrant>(cm =>
{
cm.AutoMap();
cm.SetIgnoreExtraElements(true);
});
services.AddScoped<IPersistedGrantDbContext, CustomPersistedGrantDbContext>();
services.AddTransient<IPersistedGrantStore, PersistedGrantStore>();
services.AddTransient<TokenCleanup>();
services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionJobFactory();
if (tokenCleanUpOptions.Enable)
{
var jobKey = new JobKey("TokenCleanupJob");
q.AddJob<TokenCleanupJob>(opts => opts.WithIdentity(jobKey));
q.AddTrigger(opts => opts
.ForJob(jobKey)
.WithIdentity("TokenCleanup-trigger")
.WithSimpleSchedule((s) => s.RepeatForever().WithIntervalInSeconds(tokenCleanUpOptions.Interval)));
}
});
}
}
}

View File

@@ -18,6 +18,8 @@
<PackageReference Include="MailKit" Version="3.4.3" />
<PackageReference Include="MessageBird" Version="3.2.0" />
<PackageReference Include="Ng.UserAgentService" Version="1.1.2" />
<PackageReference Include="Quartz" Version="3.5.0" />
<PackageReference Include="Quartz.AspNetCore" Version="3.5.0" />
<PackageReference Include="Scriban" Version="5.5.1" />
<PackageReference Include="SendGrid" Version="9.24.4" />
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />