IdentityServer est une solution open source .NET de gestion d’identité et de contrôle d’accès. Il repose sur les protocoles OpenID Connect et OAuth 2.0. IdentityServer peut être utilisé par les entreprises pour mettre en place une solution pour :
- la protection de leurs ressources ;
- l’authentification des utilisateurs via une base de données ou des fournisseurs externes d’identité (Microsoft, Google, Facebook, etc.) ;
- la gestion des sessions et la fédération (single sign-on) ;
- la génération des jetons pour les clients ;
- la validation des jetons et bien plus.
Ce billet est le neuvième que j’écris sur le sujet. Les billets précédents ont porté sur les points suivants :
Mise en place d’un STS avec IdentityServer4 pour sécuriser ses applications .NET
Sécurisation d’une Web API ASP.NET Core avec le STS IdentityServer4
IdentityServer4 : création et configuration du Client pour accéder à une Web API ASP.NET Core sécurisée
IdentityServer4 : Authentification d’un utilisateur avec OpenID Connect
IdentityServer4 : création et configuration d’un client utilisant OpenID Connect
IdentityServer4 : Autoriser l’application MVC à accéder à l’API, via le jeton obtenu du STS
IdentityServer4 : prise en charge du provider tiers Microsoft pour l'authentification
IdentityServer4 : prise en charge du provider tiers Github pour l'authentificationDepuis le début de notre ballade dans l’univers fascinant d’IdentityServer, nous avons utilisé une classe Config pour définir les informations de configuration de notre serveur de gestion d’identité :
| Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | public class Config { public static IEnumerable<Client> GetClients() { return new List<Client> { new Client { ClientId = "consoleappclient", AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = { "testapi" } }, // OpenID Connect implicit flow client (MVC) new Client { ClientId = "mvcappclient", ClientName = "MVC Client", AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, RequireConsent = false, RedirectUris = { "https://localhost:5005/signin-oidc" }, PostLogoutRedirectUris = { "https://localhost:5005/signout-callback-oidc" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "testapi" }, AllowOfflineAccess = true } } ; } public static IEnumerable<ApiResource> GetApiResources() { return new List<ApiResource> { new ApiResource("testapi", "My Test API") }; } public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile(), }; } public static List<TestUser> GetUsers() { return new List<TestUser> { new TestUser{SubjectId = "818727", Username = "alice", Password = "alice", Claims = { new Claim(JwtClaimTypes.Name, "Alice Smith"), new Claim(JwtClaimTypes.GivenName, "Alice"), new Claim(JwtClaimTypes.FamilyName, "Smith"), new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"), new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), new Claim(JwtClaimTypes.WebSite, "http://alice.com"), new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json) } }, new TestUser{SubjectId = "88421113", Username = "bob", Password = "bob", Claims = { new Claim(JwtClaimTypes.Name, "Bob Smith"), new Claim(JwtClaimTypes.GivenName, "Bob"), new Claim(JwtClaimTypes.FamilyName, "Smith"), new Claim(JwtClaimTypes.Email, "BobSmith@email.com"), new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), new Claim(JwtClaimTypes.WebSite, "http://bob.com"), new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json), new Claim("location", "somewhere") } } }; } } |
Cette approche n’est pas très pratique, car elle ne permet pas de livrer une solution clé en main.
Dans ce billet, nous verrons comment utiliser une base de données SQL Server pour le stockage des informations de configuration et Entity Framework pour l’accès.
Support d’Entity Framework dans IdentityServer 4
IdentityServer offre un package qui fournit des API permettant d’assurer facilement la persistante des données de configuration en utilisant EntityFramework. Ces API offrent des implémentations des interfaces IClientStore et IResourceStore pour la gestion de la persistance des clients, des ressources d’API et des ressources d’identité. Ces dernières utilisent la classe ConfigurationDbContext qui hérite du DbContext.
Pour commencer, nous allons installer le package IdentityServer4.EntityFramework à notre application IdentityServer, via le gestionnaire de packages :
Vous devez aussi installer le package Microsoft.EntityFrameworkCore.Sqlite.
Configuration du Store
Nous allons utiliser une base de données SQLite pour le stockage de nos données. Après l’ajout des packages, la seconde chose à faire est l’ajout d’un fichier de paramètres d’application (appsettings.json) pour la définition de la chaine de connexion :
| Code json : | Sélectionner tout |
1 2 3 4 5 | { "ConnectionStrings": { "DefaultConnection": "Data Source=IdentityServer.db" } } |
| Code c# : | Sélectionner tout |
1 2 3 4 5 6 | public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } |
| Code c# : | Sélectionner tout |
1 2 3 4 5 6 | .AddConfigurationStore(options => { options.ConfigureDbContext = builder => builder.UseSqlite(Configuration.GetConnectionString("DefaultConnection"), sql => sql.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name)); }) |
| Code c# : | Sélectionner tout |
1 2 3 | using Microsoft.Extensions.Configuration; using Microsoft.EntityFrameworkCore; using System.Reflection; |
| Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); //configure identity server with in-memory stores, keys, clients and resources services.AddIdentityServer() .AddDeveloperSigningCredential() .AddConfigurationStore(options => { options.ConfigureDbContext = builder => builder.UseSqlite(Configuration.GetConnectionString("DefaultConnection"), sql => sql.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name)); }) .AddTestUsers(Config.GetUsers()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage() ; } app.UseStaticFiles(); app.UseIdentityServer(); app.UseMvcWithDefaultRoute(); } } |
Le package IdentityServer4.EntityFramework contient les classes d’entité correspondant au modèle de données de configuration d’EntityFramework. Nous allons utiliser la migration et les outils Entity Framework en ligne de commande pour créer la base de données avec un schéma de données correspondant à celui défini par ce package.
Depuis la version 2.1.3 du kit de développement de .NET Core, les commandes dotnet ef sont intégrées par défaut et supportent Entity Framework Core 2.0 et versions ultérieures. Si vous avez une version égale ou supérieure à cette version, aucune installation supplémentaire n’est nécessaire pour pouvoir utiliser les outils en ligne de commande. Sinon, vous devez installer le package Microsoft.EntityFrameworkCore.Tools.DotNet.
L’approche utilisée ici est CodeFirst. Nous devons exécuter la commande « dotnet ef migrations add » en invite de commande à partir du dossier contenant le fichier .csproj. Cette commande va permettre de générer le code correspondant au modèle de données.
La commande complète à exécuter est la suivante :
| Code : | Sélectionner tout |
1 2 | dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c ConfigurationDbContext -o Data/Migrations/IdentityServer/ConfigurationDb |
| Code : | Sélectionner tout |
Dotnet ef database update
Une fois cette commande exécutée, un fichier de base de données « IdentityServer.db » sera créé à la racine du dossier du projet. Si vous ouvrez ce fichier avec « DB Browser for SQLite », vous aurez les tables suivantes :
Initialisation de la base de données
Nous allons maintenant initialiser la base de données avec les données qui ont été définies dans le fichier Config.cs. Vous devez éditer le fichier Startup.cs et ajouter la méthode suivante :
| Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | private void InitializeDatabase(IApplicationBuilder app) { using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope()) { var context = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>(); if (!context.Clients.Any()) { foreach (var client in Config.GetClients()) { context.Clients.Add(client.ToEntity()); } context.SaveChanges(); } if (!context.IdentityResources.Any()) { foreach (var resource in Config.GetIdentityResources()) { context.IdentityResources.Add(resource.ToEntity()); } context.SaveChanges(); } if (!context.ApiResources.Any()) { foreach (var resource in Config.GetApiResources()) { context.ApiResources.Add(resource.ToEntity()); } context.SaveChanges(); } } } |
| Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) { InitializeDatabase(app); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage() ; } app.UseStaticFiles(); app.UseIdentityServer(); app.UseMvcWithDefaultRoute(); } |
Désormais, si vous avez une nouvelle ressource ou un nouveau client à configurer, vous devez simplement enregistrer ce dernier dans la base de données pour que celui-ci soit pris en charge par IdentityServer.
Dans le prochain billet, nous verrons comment utiliser IdentityServer avec ASP.NET Core Identity.
Vous avez lu gratuitement 2 886 articles depuis plus d'un an.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.