Using an EF Core database for the IdentityServer4 configuration data

This article shows how to implement a database store for the IdentityServer4 configurations for the Client, ApiResource and IdentityResource settings using Entity Framework Core and SQLite. This could be used, if you need to create clients, or resources dynamically for the STS, or if you need to deploy the STS to multiple instances, for example using Service Fabric. To make it scalable, you need to remove all session data, and configuration data from the STS instances and share this in a shared resource, otherwise you can run it only smoothly as a single instance.

Information about IdentityServer4 deployment can be found here:

http://docs.identityserver.io/en/release/topics/deployment.html

Code: https://github.com/damienbod/AspNetCoreIdentityServer4Persistence

Implementing the IClientStore

By implementing the IClientStore, you can load your STS client data from anywhere you want. This example uses an Entity Framework Core Context, to load the data from a SQLite database.

using IdentityServer4.Models;
using IdentityServer4.Stores;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace AspNetCoreIdentityServer4Persistence.ConfigurationStore
{
    public class ClientStore : IClientStore
    {
        private readonly ConfigurationStoreContext _context;
        private readonly ILogger _logger;

        public ClientStore(ConfigurationStoreContext context, ILoggerFactory loggerFactory)
        {
            _context = context;
            _logger = loggerFactory.CreateLogger("ClientStore");
        }

        public Task FindClientByIdAsync(string clientId)
        {
            var client = _context.Clients.First(t => t.ClientId == clientId);
            client.MapDataFromEntity();
            return Task.FromResult(client.Client);
        }
    }
}

The ClientEntity is used to save or retrieve the data from the database. Because the IdentityServer4 class cannot be saved directly using Entity Framework Core, a wrapper class is used which saves the Client object as a Json string. The entity class implements helper methods, which parses the Json string to/from the type Client class, which is used by Identityserver4.

using IdentityServer4.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;

namespace AspNetCoreIdentityServer4Persistence.ConfigurationStore
{
    public class ClientEntity
    {
        public string ClientData { get; set; }

        [Key]
        public string ClientId { get; set; }

        [NotMapped]
        public Client Client { get; set; }

        public void AddDataToEntity()
        {
            ClientData = JsonConvert.SerializeObject(Client);
            ClientId = Client.ClientId;
        }

        public void MapDataFromEntity()
        {
            Client = JsonConvert.DeserializeObject(ClientData);
            ClientId = Client.ClientId;
        }
    }
}

Teh ConfigurationStoreContext implements the Entity Framework class to access the SQLite database. This could be easily changed to any other database supported by Entity Framework Core.

using IdentityServer4.Models;
using Microsoft.EntityFrameworkCore;

namespace AspNetCoreIdentityServer4Persistence.ConfigurationStore
{
    public class ConfigurationStoreContext : DbContext
    {
        public ConfigurationStoreContext(DbContextOptions options) : base(options)
        { }

        public DbSet Clients { get; set; }
        public DbSet ApiResources { get; set; }
        public DbSet IdentityResources { get; set; }
        

        protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.Entity().HasKey(m => m.ClientId);
            builder.Entity().HasKey(m => m.ApiResourceName);
            builder.Entity().HasKey(m => m.IdentityResourceName);
            base.OnModelCreating(builder);
        }
    }
}

Implementing the IResourceStore

The IResourceStore interface is used to save or access the ApiResource configurations and the IdentityResource data in the IdentityServer4 application. This is implemented in a similiar way to the IClientStore.

using IdentityServer4.Models;
using IdentityServer4.Stores;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace AspNetCoreIdentityServer4Persistence.ConfigurationStore
{
    public class ResourceStore : IResourceStore
    {
        private readonly ConfigurationStoreContext _context;
        private readonly ILogger _logger;

        public ResourceStore(ConfigurationStoreContext context, ILoggerFactory loggerFactory)
        {
            _context = context;
            _logger = loggerFactory.CreateLogger("ResourceStore");
        }

        public Task FindApiResourceAsync(string name)
        {
            var apiResource = _context.ApiResources.First(t => t.ApiResourceName == name);
            apiResource.MapDataFromEntity();
            return Task.FromResult(apiResource.ApiResource);
        }

        public Task<IEnumerable> FindApiResourcesByScopeAsync(IEnumerable scopeNames)
        {
            if (scopeNames == null) throw new ArgumentNullException(nameof(scopeNames));


            var apiResources = new List();
            var apiResourcesEntities = from i in _context.ApiResources
                                            where scopeNames.Contains(i.ApiResourceName)
                                            select i;

            foreach (var apiResourceEntity in apiResourcesEntities)
            {
                apiResourceEntity.MapDataFromEntity();

                apiResources.Add(apiResourceEntity.ApiResource);
            }

            return Task.FromResult(apiResources.AsEnumerable());
        }

        public Task<IEnumerable> FindIdentityResourcesByScopeAsync(IEnumerable scopeNames)
        {
            if (scopeNames == null) throw new ArgumentNullException(nameof(scopeNames));

            var identityResources = new List();
            var identityResourcesEntities = from i in _context.IdentityResources
                             where scopeNames.Contains(i.IdentityResourceName)
                           select i;

            foreach (var identityResourceEntity in identityResourcesEntities)
            {
                identityResourceEntity.MapDataFromEntity();

                identityResources.Add(identityResourceEntity.IdentityResource);
            }

            return Task.FromResult(identityResources.AsEnumerable());
        }

        public Task GetAllResourcesAsync()
        {
            var apiResourcesEntities = _context.ApiResources.ToList();
            var identityResourcesEntities = _context.IdentityResources.ToList();

            var apiResources = new List();
            var identityResources= new List();

            foreach (var apiResourceEntity in apiResourcesEntities)
            {
                apiResourceEntity.MapDataFromEntity();

                apiResources.Add(apiResourceEntity.ApiResource);
            }

            foreach (var identityResourceEntity in identityResourcesEntities)
            {
                identityResourceEntity.MapDataFromEntity();

                identityResources.Add(identityResourceEntity.IdentityResource);
            }

            var result = new Resources(identityResources, apiResources);
            return Task.FromResult(result);
        }
    }
}

The IdentityResourceEntity class is used to persist the IdentityResource data.

using IdentityServer4.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;

namespace AspNetCoreIdentityServer4Persistence.ConfigurationStore
{
    public class IdentityResourceEntity
    {
        public string IdentityResourceData { get; set; }

        [Key]
        public string IdentityResourceName { get; set; }

        [NotMapped]
        public IdentityResource IdentityResource { get; set; }

        public void AddDataToEntity()
        {
            IdentityResourceData = JsonConvert.SerializeObject(IdentityResource);
            IdentityResourceName = IdentityResource.Name;
        }

        public void MapDataFromEntity()
        {
            IdentityResource = JsonConvert.DeserializeObject(IdentityResourceData);
            IdentityResourceName = IdentityResource.Name;
        }
    }
}

The ApiResourceEntity is used to persist the ApiResource data.

using IdentityServer4.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;

namespace AspNetCoreIdentityServer4Persistence.ConfigurationStore
{
    public class ApiResourceEntity
    {
        public string ApiResourceData { get; set; }

        [Key]
        public string ApiResourceName { get; set; }

        [NotMapped]
        public ApiResource ApiResource { get; set; }

        public void AddDataToEntity()
        {
            ApiResourceData = JsonConvert.SerializeObject(ApiResource);
            ApiResourceName = ApiResource.Name;
        }

        public void MapDataFromEntity()
        {
            ApiResource = JsonConvert.DeserializeObject(ApiResourceData);
            ApiResourceName = ApiResource.Name;
        }
    }
}

Adding the stores to the IdentityServer4 MVC startup class

The created stores can now be used and added to the Startup class of the ASP.NET Core MVC host project for IdentityServer4. The AddDbContext method is used to setup the Entity Framework Core data access and the AddResourceStore as well as AddClientStore are used to add the configuration data to IdentityServer4. The two interfaces and also the implementations need to be registered with the IoC.

The default AddInMemory… extension methods are removed.

public void ConfigureServices(IServiceCollection services)
{
	services.AddDbContext(options =>
		options.UseSqlite(
			Configuration.GetConnectionString("ConfigurationStoreConnection"),
			b => b.MigrationsAssembly("AspNetCoreIdentityServer4")
		)
	);

	...

	services.AddTransient();
	services.AddTransient();

	services.AddIdentityServer()
		.AddSigningCredential(cert)
		.AddResourceStore()
		.AddClientStore()
		.AddAspNetIdentity()
		.AddProfileService();

}

Seeding the database

A simple .NET Core console application is used to seed the STS server with data. This class creates the different Client, ApiResources and IdentityResources as required. The data is added directly to the database using Entity Framework Core. If this was a micro service, you would implement an API on the STS server which adds, removes, updates the data as required.

static void Main(string[] args)
{
	try
	{
		var currentDirectory = Directory.GetCurrentDirectory();

		var configuration = new ConfigurationBuilder()
			.AddJsonFile($"{currentDirectory}\..\AspNetCoreIdentityServer4\appsettings.json")
			.Build();

		var configurationStoreConnection = configuration.GetConnectionString("ConfigurationStoreConnection");

		var optionsBuilder = new DbContextOptionsBuilder();
		optionsBuilder.UseSqlite(configurationStoreConnection);

		using (var configurationStoreContext = new ConfigurationStoreContext(optionsBuilder.Options))
		{
			configurationStoreContext.AddRange(Config.GetClients());
			configurationStoreContext.AddRange(Config.GetIdentityResources());
			configurationStoreContext.AddRange(Config.GetApiResources());
			configurationStoreContext.SaveChanges();
		}
	}
	catch (Exception e)
	{
		Console.WriteLine(e.Message);
	}

	Console.ReadLine();
}

The static Config class just adds the data like the IdentityServer4 examples.

Now the applications run using the configuration data stored in an Entity Framwork Core supported database.

Links:

http://docs.identityserver.io/en/release/topics/deployment.html

https://damienbod.com/2016/01/07/experiments-with-entity-framework-7-and-asp-net-5-mvc-6/

https://docs.microsoft.com/en-us/ef/core/get-started/netcore/new-db-sqlite

https://docs.microsoft.com/en-us/ef/core/

damienbod责编内容来自:damienbod (源链) | 更多关于

阅读提示:酷辣虫无法对本内容的真实性提供任何保证,请自行验证并承担相关的风险与后果!
本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 后端存储 » Using an EF Core database for the IdentityServer4 configuration data

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录