Хранение Токена (IdentityServer4) в базе данных (pgSQL)

215
10 мая 2022, 07:00

Мобильное приложение через IdentityServer подключается к микросервису и затем через API(s) к базе данных. Все работает. Кроме бага, который обнаружен (иногда), мобильное приложение по истечении валидности токена или при перезапуске Identity выдает ошибку или просит зайти заново. В следствии чего, понятно, что токен кешируется локально и иногда теряется. Я новичок, и так я это вижу

Решение: Нужно сохранять токен в базе данных, чтобы при надобности IdentityServer мог запросить и тем самым баг решен✔ ВСЕ РАБОТАЕТ.

В данном вопросе хотелось бы рассмотреть возможное решение для элегантности кода. Ссылки откуда я брала информацию :

  • Документация

  • Изменения двух моделей, которые не имеют по умолчанию ID

    modelBuilder.Entity<DeviceFlowCodes>().HasNoKey();
    modelBuilder.Entity<PersistedGrant>().HasNoKey();
   //в моем случае не позволяло сохранить токен, поля были readOnly
  • Очень интересная лекция/доклад

над приложением я не одна работаю, и ниже прикладываю код, который я добавила уже к рабочему IdentityServer

добавила NuGet:

IdentityServer4 3.1.4
IdentityServer4.EntityFramework.Storage 3.1.4
Npgsql.EntityFrameworkCore.PostgreSQL 3.1.4
IdentityServer4.EntityFramework 3.1.4
public class IdentityServer4DbContext : IPersistedGrantDbContext, IConfigurationDbContext ...
{
    public DbSet<PersistedGrant> PersistedGrants { get; set; }
     
    public DbSet<DeviceFlowCodes> DeviceFlowCodes { get; set; }
        
    public DbSet<Client> Clients { get; set; }
      
    public DbSet<IdentityResource> IdentityResources { get; set; }
        
    public DbSet<ApiResource> ApiResources { get; set; }

...
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    base.OnConfiguring(optionsBuilder);
    optionsBuilder.UseNpgsql(configuration.ConnectionString, o => o.MigrationsAssembly(typeof(Program).Assembly.GetName().Name));
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
     base.OnModelCreating(modelBuilder);
     if (modelBuilder is null)
         throw new ArgumentNullException(nameof(modelBuilder));
     modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
     {
          b.Property<string>("UserCode")
              .ValueGeneratedOnAdd()
              .HasMaxLength(200);
          b.Property<string>("ClientId")
              .IsRequired()
              .HasMaxLength(200);
          b.Property<DateTime>("CreationTime");
          b.Property<string>("Data")
              .IsRequired()
              .HasMaxLength(50000);
          b.Property<string>("DeviceCode")
              .IsRequired()
              .HasMaxLength(200);
          b.Property<DateTime?>("Expiration")
              .IsRequired();
          b.Property<string>("SubjectId")
               .HasMaxLength(200);
          b.HasKey("UserCode");
          b.HasIndex("DeviceCode")
               .IsUnique();
          b.HasIndex("UserCode")
               .IsUnique();
          b.ToTable("DeviceCodes");
    });
    modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
    {
         b.Property<string>("Key")
             .HasMaxLength(200);
         b.Property<string>("ClientId")
             .IsRequired()
             .HasMaxLength(200);
         b.Property<DateTime>("CreationTime");
         b.Property<string>("Data")
             .IsRequired()
             .HasMaxLength(50000);
         b.Property<DateTime?>("Expiration");
         b.Property<string>("SubjectId")
             .HasMaxLength(200);
         b.Property<string>("Type")
             .IsRequired()
             .HasMaxLength(50);
         b.HasKey("Key");
         b.HasIndex("SubjectId", "ClientId", "Type");
         b.ToTable("PersistedGrants");
     });
  }
Task<int> IPersistedGrantDbContext.SaveChangesAsync() => base.SaveChangesAsync();
public Task<int> SaveChangesAsync() => base.SaveChangesAsync();
public void ConfigureServices(IServiceCollection services)
{
...
services.AddConfigurationStore<IdentityServer4DbContext>()
        .AddOperationalStore<IdentityServer4DbContext>(options => {
            // this enables automatic token cleanup.
            options.EnableTokenCleanup = true;
            options.TokenCleanupInterval = 43200; // 43200 = 12 hours. interval in seconds (default is 3600)
            });
...
}
private async void InitializeDatabase(IApplicationBuilder app)
{
.....
IdentityServer4DbContext dbContext = serviceScope.ServiceProvider.GetRequiredService<IdentityServer4DbContext>();
dbContext.Database.Migrate();
.....
    if (!dbContext.Clients.Any())
    {
        foreach (var client in Config.Clients)
        {
            dbContext.Clients.Add(client.ToEntity());
        }
        await dbContext.SaveChangesAsync().ConfigureAwait(false);
    }
    if (!dbContext.IdentityResources.Any())
    {
        foreach (var resource in Config.Ids)
        {
            dbContext.IdentityResources.Add(resource.ToEntity());
        }
        await dbContext.SaveChangesAsync().ConfigureAwait(false);
    }
    if (!dbContext.ApiResources.Any())
    {
        foreach (var resource in Config.Apis)
        {
            dbContext.ApiResources.Add(resource.ToEntity());
        }
        await dbContext.SaveChangesAsync().ConfigureAwait(false);
    }
}

затем миграция и в результате созданы таблицы с данными и токенами, при вхождении с мобильного и никаких ошибок, работает и тестировала много, перезагружала Identity пока мобильное приложение работало и наоборот, и дургими способами, которые раньше выдавали ошибку. То есть код рабочий, но вот в чем мой вопрос профессионалам: Та часть, где вручную set-ирую две модели (DeviceFlowCodes, PersistedGrant), есть ли какой то вариант создать класс отдельно как модель, так как я пыталась, но у меня не вышло, и пока я не вижу как вытащить этот код наружу из OnModelCreating Хотелось бы услышать различные точки зрения и возможно замечания.

Answer 1

Статические методы, которые позволяют проделать то же самое, и вуаля, эллегантное решение кода. Проверено, работает также и проведенно немало тестов ✔

READ ALSO
Запуск dll .NET 5 из своего кода

Запуск dll .NET 5 из своего кода

Хочу запустить dll написаннуюNET 5 из своего кода тоже на

172
&quot;включение&quot; и &quot;выключение&quot; эффекта

"включение" и "выключение" эффекта

При нажатии на объект - объект должен "исчезать", а на его месте должен появляться эффект

147