From 8be71b91f117858ee577302e2804675468160f9f Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Mon, 3 Oct 2022 19:15:56 +0300 Subject: [PATCH 01/16] added keycloak image to docker compose --- .../docker-compose.infrastructure.override.yml | 14 +++++++++++++- etc/docker/docker-compose.infrastructure.yml | 15 ++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/etc/docker/docker-compose.infrastructure.override.yml b/etc/docker/docker-compose.infrastructure.override.yml index 3ae3deb5..22018264 100644 --- a/etc/docker/docker-compose.infrastructure.override.yml +++ b/etc/docker/docker-compose.infrastructure.override.yml @@ -1,4 +1,4 @@ -version: '3.7' +version: '3.8' services: rabbitmq: @@ -18,6 +18,7 @@ services: ports: - "5432:5432" environment: + POSTGRES_DB: keycloak POSTGRES_PASSWORD: "myPassw0rd" pgadmin: @@ -26,3 +27,14 @@ services: environment: PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org} PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin} + + keycloak: + ports: + - "44320:8080" + environment: + DB_VENDOR: postgres + DB_ADDR: "postgres-db" + DB_DATABASE: "keycloak" + DB_USER: "postgres" + DB_PASSWORD: "myPassw0rd" + \ No newline at end of file diff --git a/etc/docker/docker-compose.infrastructure.yml b/etc/docker/docker-compose.infrastructure.yml index 547b3aae..800073f8 100644 --- a/etc/docker/docker-compose.infrastructure.yml +++ b/etc/docker/docker-compose.infrastructure.yml @@ -1,4 +1,4 @@ -version: '3.7' +version: '3.8' services: rabbitmq: @@ -46,12 +46,21 @@ services: - eshoponabp-network pgadmin: - container_name: pgadmin_container + container_name: pgadmin image: dpage/pgadmin4:6.2 volumes: - pgadmin_data:/var/lib/pgadmin networks: - - eshoponabp-network + - eshoponabp-network + + keycloak: + container_name: keycloak + image: jboss/keycloak:13.0.0 + depends_on: + - postgres-db + restart: unless-stopped + networks: + - eshoponabp-network volumes: postgres_data: From b2954cfaae1307a87d8c5148049aaf3857134c96 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Wed, 5 Oct 2022 15:38:42 +0300 Subject: [PATCH 02/16] keycloak docker image update --- etc/docker/docker-compose.infrastructure.override.yml | 5 +++++ etc/docker/docker-compose.infrastructure.yml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/etc/docker/docker-compose.infrastructure.override.yml b/etc/docker/docker-compose.infrastructure.override.yml index 22018264..249946e7 100644 --- a/etc/docker/docker-compose.infrastructure.override.yml +++ b/etc/docker/docker-compose.infrastructure.override.yml @@ -37,4 +37,9 @@ services: DB_DATABASE: "keycloak" DB_USER: "postgres" DB_PASSWORD: "myPassw0rd" + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: admin + KC_HEALTH_ENABLED: true + entrypoint: ["/opt/keycloak/bin/kc.sh", "start-dev"] + \ No newline at end of file diff --git a/etc/docker/docker-compose.infrastructure.yml b/etc/docker/docker-compose.infrastructure.yml index 800073f8..3f72762f 100644 --- a/etc/docker/docker-compose.infrastructure.yml +++ b/etc/docker/docker-compose.infrastructure.yml @@ -55,7 +55,7 @@ services: keycloak: container_name: keycloak - image: jboss/keycloak:13.0.0 + image: quay.io/keycloak/keycloak:19.0.2 depends_on: - postgres-db restart: unless-stopped From fdd9e23221c44df132785c4434856cba50b4ba8d Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Thu, 6 Oct 2022 10:06:32 +0300 Subject: [PATCH 03/16] added configuration --- .../EShopOnAbpPublicWebModule.cs | 79 ++++++++++++++----- .../src/EShopOnAbp.PublicWeb/appsettings.json | 8 ++ ...docker-compose.infrastructure.override.yml | 4 +- .../appsettings.json | 4 +- 4 files changed, 72 insertions(+), 23 deletions(-) diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs b/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs index 58e1f408..0bc83acb 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs @@ -141,27 +141,68 @@ public class EShopOnAbpPublicWebModule : AbpModule .AddCookie("Cookies", options => { options.ExpireTimeSpan = TimeSpan.FromDays(365); }) .AddAbpOpenIdConnect("oidc", options => { - options.Authority = configuration["AuthServer:Authority"]; - options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); - options.ResponseType = OpenIdConnectResponseType.CodeIdToken; - - options.ClientId = configuration["AuthServer:ClientId"]; - options.ClientSecret = configuration["AuthServer:ClientSecret"]; - - options.SaveTokens = true; + /* + * ASP.NET core uses the http://*:5000 and https://*:5001 ports for default communication with the OIDC middleware + * The app requires load balancing services to work with :80 or :443 + * These needs to be added to the keycloak client, in order for the redirect to work. + * If you however intend to use the app by itself then, + * Change the ports in launchsettings.json, but beware to also change the options.CallbackPath and options.SignedOutCallbackPath! + * Use LB services whenever possible, to reduce the config hazzle :) + */ + + //Use default signin scheme + // options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; + //Keycloak server + options.Authority = configuration["Keycloak:ServerRealm"]; + //Keycloak client ID + options.ClientId = configuration["Keycloak:ClientId"]; + //Keycloak client secret + options.ClientSecret = configuration["Keycloak:ClientSecret"]; + //Keycloak .wellknown config origin to fetch config + options.MetadataAddress = configuration["Keycloak:Metadata"]; + //Require keycloak to use SSL + options.RequireHttpsMetadata = false; options.GetClaimsFromUserInfoEndpoint = true; - - options.Scope.Add("role"); - options.Scope.Add("email"); - options.Scope.Add("phone"); - options.Scope.Add("AccountService"); - options.Scope.Add("AdministrationService"); - options.Scope.Add("BasketService"); - options.Scope.Add("CatalogService"); - options.Scope.Add("PaymentService"); - options.Scope.Add("OrderingService"); - options.Scope.Add("CmskitService"); + options.Scope.Add("openid"); + options.Scope.Add("profile"); + //Save the token + options.SaveTokens = true; + //Token response type, will sometimes need to be changed to IdToken, depending on config. + options.ResponseType = OpenIdConnectResponseType.Code; + //SameSite is needed for Chrome/Firefox, as they will give http error 500 back, if not set to unspecified. + // options.NonceCookie.SameSite = SameSiteMode.Unspecified; + // options.CorrelationCookie.SameSite = SameSiteMode.Unspecified; + // + // options.TokenValidationParameters = new TokenValidationParameters + // { + // NameClaimType = "name", + // RoleClaimType = ClaimTypes.Role, + // ValidateIssuer = true + // }; }); + // .AddAbpOpenIdConnect("oidc", options => + // { + // options.Authority = configuration["AuthServer:Authority"]; + // options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); + // options.ResponseType = OpenIdConnectResponseType.CodeIdToken; + // + // options.ClientId = configuration["AuthServer:ClientId"]; + // options.ClientSecret = configuration["AuthServer:ClientSecret"]; + // + // options.SaveTokens = true; + // options.GetClaimsFromUserInfoEndpoint = true; + // + // options.Scope.Add("role"); + // options.Scope.Add("email"); + // options.Scope.Add("phone"); + // options.Scope.Add("AccountService"); + // options.Scope.Add("AdministrationService"); + // options.Scope.Add("BasketService"); + // options.Scope.Add("CatalogService"); + // options.Scope.Add("PaymentService"); + // options.Scope.Add("OrderingService"); + // options.Scope.Add("CmskitService"); + // }); if (Convert.ToBoolean(configuration["AuthServer:IsOnProd"])) { context.Services.Configure("oidc", options => diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json b/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json index 8d0d9e5b..bcf0d7ef 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json @@ -43,6 +43,14 @@ "IsOnProd": "false", "MetaAddress": "https://localhost:44330" }, + "Keycloak": { + "ServerRealm": "http://localhost:8080/realms/master", + "Metadata": "http://localhost:8080/realms/master/.well-known/openid-configuration", + "ClientId": "PublicWeb", + "ClientSecret": "mPpj650ADqHwQ0g9qvwWNxCqQmefrGw7", + "TokenExchange": "http://localhost:8080/realms/master/protocol/openid-connect/token", + "Audience": "some-audience" + }, "ReverseProxy": { "Routes": { "route1" : { diff --git a/etc/docker/docker-compose.infrastructure.override.yml b/etc/docker/docker-compose.infrastructure.override.yml index 249946e7..c3c7ddeb 100644 --- a/etc/docker/docker-compose.infrastructure.override.yml +++ b/etc/docker/docker-compose.infrastructure.override.yml @@ -30,7 +30,7 @@ services: keycloak: ports: - - "44320:8080" + - "8080:8080" environment: DB_VENDOR: postgres DB_ADDR: "postgres-db" @@ -41,5 +41,5 @@ services: KEYCLOAK_ADMIN_PASSWORD: admin KC_HEALTH_ENABLED: true entrypoint: ["/opt/keycloak/bin/kc.sh", "start-dev"] - + \ No newline at end of file diff --git a/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/appsettings.json b/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/appsettings.json index 8610b450..620ba213 100644 --- a/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/appsettings.json +++ b/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/appsettings.json @@ -4,8 +4,8 @@ "CorsOrigins": "https://localhost:44372,https://localhost:44373" }, "AuthServer": { - "Authority": "https://localhost:44330", - "RequireHttpsMetadata": "true", + "Authority": "http://localhost:8080/realms/master", + "RequireHttpsMetadata": "false", "SwaggerClientId": "WebGateway_Swagger", "SwaggerClientSecret": "1q2w3e*" }, From be6dbd4c9e611eaf576d05e30c4cae47c1566e96 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Thu, 6 Oct 2022 21:49:41 +0300 Subject: [PATCH 04/16] updated openid configurations --- .../EShopOnAbpPublicWebModule.cs | 15 ++++++++++++++- .../src/EShopOnAbp.PublicWeb/appsettings.json | 1 - 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs b/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs index 0bc83acb..a0aec3a1 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs @@ -23,6 +23,7 @@ using Polly; using StackExchange.Redis; using System; using System.Net.Http.Headers; +using Microsoft.AspNetCore.Authentication.OAuth.Claims; using Volo.Abp; using Volo.Abp.Account; using Volo.Abp.AspNetCore.Authentication.OpenIdConnect; @@ -42,6 +43,7 @@ using Volo.Abp.Http.Client; using Volo.Abp.Http.Client.IdentityModel.Web; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; +using Volo.Abp.Security.Claims; using Volo.Abp.UI.Navigation; using Volo.Abp.UI.Navigation.Urls; using Volo.Abp.VirtualFileSystem; @@ -157,7 +159,7 @@ public class EShopOnAbpPublicWebModule : AbpModule //Keycloak client ID options.ClientId = configuration["Keycloak:ClientId"]; //Keycloak client secret - options.ClientSecret = configuration["Keycloak:ClientSecret"]; + // options.ClientSecret = configuration["Keycloak:ClientSecret"]; //Keycloak .wellknown config origin to fetch config options.MetadataAddress = configuration["Keycloak:Metadata"]; //Require keycloak to use SSL @@ -165,6 +167,10 @@ public class EShopOnAbpPublicWebModule : AbpModule options.GetClaimsFromUserInfoEndpoint = true; options.Scope.Add("openid"); options.Scope.Add("profile"); + options.Scope.Add("email"); + options.Scope.Add("phone"); + options.Scope.Add("roles"); + options.Scope.Add("offline_access"); //Save the token options.SaveTokens = true; //Token response type, will sometimes need to be changed to IdToken, depending on config. @@ -179,6 +185,13 @@ public class EShopOnAbpPublicWebModule : AbpModule // RoleClaimType = ClaimTypes.Role, // ValidateIssuer = true // }; + + if (AbpClaimTypes.UserName != "preferred_username") + { + options.ClaimActions.MapJsonKey(AbpClaimTypes.UserName, "preferred_username"); + options.ClaimActions.DeleteClaim("preferred_username"); + options.ClaimActions.RemoveDuplicate(AbpClaimTypes.UserName); + } }); // .AddAbpOpenIdConnect("oidc", options => // { diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json b/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json index bcf0d7ef..e82d3b47 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json @@ -47,7 +47,6 @@ "ServerRealm": "http://localhost:8080/realms/master", "Metadata": "http://localhost:8080/realms/master/.well-known/openid-configuration", "ClientId": "PublicWeb", - "ClientSecret": "mPpj650ADqHwQ0g9qvwWNxCqQmefrGw7", "TokenExchange": "http://localhost:8080/realms/master/protocol/openid-connect/token", "Audience": "some-audience" }, From 0306146bad35e55ec8aa2f448603ced341df0f10 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Fri, 7 Oct 2022 16:22:14 +0300 Subject: [PATCH 05/16] Added keycloak migration service --- EShopOnAbp.sln | 7 ++ ...docker-compose.infrastructure.override.yml | 4 +- .../DbMigratorHostedService.cs | 48 ++++++++++ .../EShopOnAbp.Keycloak.DbMigrator.csproj | 34 +++++++ .../EShopOnAbpDbMigratorModule.cs | 21 +++++ .../KeyCloakDataSeeder.cs | 21 +++++ .../Keycloak/KeycloakClientOptions.cs | 6 ++ .../Keycloak/KeycloakService.cs | 94 +++++++++++++++++++ .../Keycloak/Models/AccessTokenResult.cs | 16 ++++ .../MigrationService.cs | 30 ++++++ .../EShopOnAbp.Keycloak.DbMigrator/Program.cs | 35 +++++++ .../appsettings.json | 3 + 12 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 shared/EShopOnAbp.Keycloak.DbMigrator/DbMigratorHostedService.cs create mode 100644 shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj create mode 100644 shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbpDbMigratorModule.cs create mode 100644 shared/EShopOnAbp.Keycloak.DbMigrator/KeyCloakDataSeeder.cs create mode 100644 shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/KeycloakClientOptions.cs create mode 100644 shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/KeycloakService.cs create mode 100644 shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/Models/AccessTokenResult.cs create mode 100644 shared/EShopOnAbp.Keycloak.DbMigrator/MigrationService.cs create mode 100644 shared/EShopOnAbp.Keycloak.DbMigrator/Program.cs create mode 100644 shared/EShopOnAbp.Keycloak.DbMigrator/appsettings.json diff --git a/EShopOnAbp.sln b/EShopOnAbp.sln index e7a4264d..73d5919b 100644 --- a/EShopOnAbp.sln +++ b/EShopOnAbp.sln @@ -43,6 +43,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EShopOnAbp.BasketService", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EShopOnAbp.CmskitService.HttpApi.Host", "services\cmskit\src\EShopOnAbp.CmskitService.HttpApi.Host\EShopOnAbp.CmskitService.HttpApi.Host.csproj", "{D5B9D5A5-44AA-42F8-867C-95B54780C9DC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EShopOnAbp.Keycloak.DbMigrator", "shared\EShopOnAbp.Keycloak.DbMigrator\EShopOnAbp.Keycloak.DbMigrator.csproj", "{774C6ADF-BDD0-431C-A9F3-8BAFD5A49C8C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -113,6 +115,10 @@ Global {D5B9D5A5-44AA-42F8-867C-95B54780C9DC}.Debug|Any CPU.Build.0 = Debug|Any CPU {D5B9D5A5-44AA-42F8-867C-95B54780C9DC}.Release|Any CPU.ActiveCfg = Release|Any CPU {D5B9D5A5-44AA-42F8-867C-95B54780C9DC}.Release|Any CPU.Build.0 = Release|Any CPU + {774C6ADF-BDD0-431C-A9F3-8BAFD5A49C8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {774C6ADF-BDD0-431C-A9F3-8BAFD5A49C8C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {774C6ADF-BDD0-431C-A9F3-8BAFD5A49C8C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {774C6ADF-BDD0-431C-A9F3-8BAFD5A49C8C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -134,6 +140,7 @@ Global {7AE4C613-780E-4DA5-9B57-76F0D40D5146} = {F415FFFD-E52D-4EBE-98DC-067C1EFFFFE3} {E373DD66-3247-4D95-B325-064BBBC337B1} = {F415FFFD-E52D-4EBE-98DC-067C1EFFFFE3} {D5B9D5A5-44AA-42F8-867C-95B54780C9DC} = {F415FFFD-E52D-4EBE-98DC-067C1EFFFFE3} + {774C6ADF-BDD0-431C-A9F3-8BAFD5A49C8C} = {B8B59303-2178-459B-91A8-DF353044E090} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {26F82565-C6A4-439D-93A4-3251E3E7D5B0} diff --git a/etc/docker/docker-compose.infrastructure.override.yml b/etc/docker/docker-compose.infrastructure.override.yml index c3c7ddeb..7b9c83a2 100644 --- a/etc/docker/docker-compose.infrastructure.override.yml +++ b/etc/docker/docker-compose.infrastructure.override.yml @@ -38,8 +38,8 @@ services: DB_USER: "postgres" DB_PASSWORD: "myPassw0rd" KEYCLOAK_ADMIN: admin - KEYCLOAK_ADMIN_PASSWORD: admin - KC_HEALTH_ENABLED: true + KEYCLOAK_ADMIN_PASSWORD: "1q2w3E*" + KC_HEALTH_ENABLED: "true" entrypoint: ["/opt/keycloak/bin/kc.sh", "start-dev"] \ No newline at end of file diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/DbMigratorHostedService.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/DbMigratorHostedService.cs new file mode 100644 index 00000000..ce020de4 --- /dev/null +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/DbMigratorHostedService.cs @@ -0,0 +1,48 @@ +using System.Threading; +using System.Threading.Tasks; +using EShopOnAbp.DbMigrator.Keycloak; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Serilog; +using Volo.Abp; + +namespace EShopOnAbp.DbMigrator; + +public class DbMigratorHostedService : IHostedService +{ + private readonly IHostApplicationLifetime _hostApplicationLifetime; + private readonly IConfiguration _configuration; + + public DbMigratorHostedService( + IHostApplicationLifetime hostApplicationLifetime, + IConfiguration configuration) + { + _hostApplicationLifetime = hostApplicationLifetime; + _configuration = configuration; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + using (var application = AbpApplicationFactory.Create(options => + { + options.Services.ReplaceConfiguration(_configuration); + options.UseAutofac(); + options.Services.AddLogging(c => c.AddSerilog()); + })) + { + application.Initialize(); + + await application + .ServiceProvider + .GetRequiredService() + .MigrateAsync(cancellationToken); + + application.Shutdown(); + + _hostApplicationLifetime.StopApplication(); + } + } + + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; +} \ No newline at end of file diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj b/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj new file mode 100644 index 00000000..98834c63 --- /dev/null +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj @@ -0,0 +1,34 @@ + + + + Exe + net6.0 + EShopOnAbp.DbMigrator + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + Always + + + + diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbpDbMigratorModule.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbpDbMigratorModule.cs new file mode 100644 index 00000000..f739f1e6 --- /dev/null +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbpDbMigratorModule.cs @@ -0,0 +1,21 @@ +using EShopOnAbp.DbMigrator.Keycloak; +using EShopOnAbp.Shared.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; + +namespace EShopOnAbp.DbMigrator; + +[DependsOn( + typeof(EShopOnAbpSharedHostingModule) +)] +public class EShopOnAbpDbMigratorModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + + context.Services.AddHttpClient(KeycloakService.HttpClientName); + + Configure(configuration); + } +} \ No newline at end of file diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/KeyCloakDataSeeder.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/KeyCloakDataSeeder.cs new file mode 100644 index 00000000..abd7dcfa --- /dev/null +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/KeyCloakDataSeeder.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; +using EShopOnAbp.DbMigrator.Keycloak; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.DbMigrator; + +public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency +{ + private readonly KeycloakService _keycloakService; + + public KeyCloakDataSeeder(KeycloakService keycloakService) + { + _keycloakService = keycloakService; + } + + public async Task SeedAsync(DataSeedContext context) + { + var result = await _keycloakService.GetAdminAccessTokenAsync("master"); + } +} \ No newline at end of file diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/KeycloakClientOptions.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/KeycloakClientOptions.cs new file mode 100644 index 00000000..417638d7 --- /dev/null +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/KeycloakClientOptions.cs @@ -0,0 +1,6 @@ +namespace EShopOnAbp.DbMigrator.Keycloak; + +public class KeycloakClientOptions +{ + +} \ No newline at end of file diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/KeycloakService.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/KeycloakService.cs new file mode 100644 index 00000000..7a8c9a70 --- /dev/null +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/KeycloakService.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using EShopOnAbp.DbMigrator.Keycloak.Models; +using Microsoft.Extensions.Logging; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.DbMigrator.Keycloak; + +public class KeycloakService : ITransientDependency +{ + public const string HttpClientName = "KeycloakServiceHttpClientName"; + + // TODO: Option + public const string BaseUrl = "http://localhost:8080/admin/realms"; + public const string AdminClientId = "admin-cli"; + public const string AdminUserName = "admin"; + public const string AdminPassword = "1q2w3E*"; + + private readonly ILogger _logger; + private readonly IHttpClientFactory _clientFactory; + + public KeycloakService(IHttpClientFactory clientFactory, ILogger logger) + { + _clientFactory = clientFactory; + _logger = logger; + } + + // public async Task CreateClientAsync(string realm, Client client) + // { + // HttpResponseMessage response = await InternalCreateClientAsync(realm, client).ConfigureAwait(false); + // + // var locationPathAndQuery = response.Headers.Location.PathAndQuery; + // var clientId = response.IsSuccessStatusCode + // ? locationPathAndQuery.Substring(locationPathAndQuery.LastIndexOf("/", StringComparison.Ordinal) + 1) + // : null; + // return clientId; + // } + + private HttpClient CreateKeycloakApiHttpClient(string realm) + { + var httpClient = _clientFactory.CreateClient(HttpClientName); + httpClient.BaseAddress = new Uri(BaseUrl); + + return httpClient; + } + + private async Task CreateKeycloakApiHttpClientAsync(string realm, string token = null) + { + var httpClient = CreateKeycloakApiHttpClient(realm); + httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); + if (!string.IsNullOrEmpty(token)) + { + var accessToken = await GetAdminAccessTokenAsync(realm); + httpClient.DefaultRequestHeaders.Authorization = + new System.Net.Http.Headers.AuthenticationHeaderValue($"Bearer", $"{accessToken}"); + } + + return httpClient; + } + + public async Task GetAdminAccessTokenAsync(string realm) + { + var httpClient = _clientFactory.CreateClient(HttpClientName); + httpClient.BaseAddress = new Uri(BaseUrl); + + var result = string.Empty; + + var formContent = new FormUrlEncodedContent(new[] + { + new KeyValuePair("client_id", AdminClientId), + new KeyValuePair("username", AdminUserName), + new KeyValuePair("password", AdminPassword), + new KeyValuePair("grant_type", "password") + }); + + var httpResponseMessage = + await httpClient.PostAsync($"/realms/{realm}/protocol/openid-connect/token", formContent); + + if (httpResponseMessage.IsSuccessStatusCode) + { + var response = await httpResponseMessage.Content.ReadFromJsonAsync(); + result = response?.AccessToken; + } + + return result; + } +} \ No newline at end of file diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/Models/AccessTokenResult.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/Models/AccessTokenResult.cs new file mode 100644 index 00000000..83d2266b --- /dev/null +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/Models/AccessTokenResult.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace EShopOnAbp.DbMigrator.Keycloak.Models; + +public class AccessTokenResult +{ + [JsonPropertyName("access_token")] public string AccessToken { get; set; } + [JsonPropertyName("expires_in")] public int Expiration { get; set; } + [JsonPropertyName("refresh_token")] public string RefreshToken { get; set; } + + [JsonPropertyName("refresh_expires_in")] + public int RefreshExpiration { get; set; } + + [JsonPropertyName("token_type")] public string TokenType { get; set; } + [JsonPropertyName("scope")] public string Scope { get; set; } +} \ No newline at end of file diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/MigrationService.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/MigrationService.cs new file mode 100644 index 00000000..aa0cf304 --- /dev/null +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/MigrationService.cs @@ -0,0 +1,30 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.DbMigrator; + +public class MigrationService: ITransientDependency +{ + private readonly ILogger _logger; + private readonly IDataSeeder _dataSeeder; + + public MigrationService(ILogger logger, IDataSeeder dataSeeder) + { + _logger = logger; + _dataSeeder = dataSeeder; + } + + public async Task MigrateAsync(CancellationToken cancellationToken) + { + // Check if keycloak api is available + + //Seed data + await _dataSeeder.SeedAsync(); + + _logger.LogInformation("Migration completed!"); + } + +} \ No newline at end of file diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/Program.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/Program.cs new file mode 100644 index 00000000..bd60e4fa --- /dev/null +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/Program.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; +using EShopOnAbp.DbMigrator; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Serilog; +using Serilog.Events; + +class Program +{ + async static Task Main(string[] args) + { + Log.Logger = new LoggerConfiguration() +#if DEBUG + .MinimumLevel.Debug() +#else + .MinimumLevel.Information() +#endif + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) + .Enrich.FromLogContext() + .Enrich.WithProperty("Application", $"DbMigrator") + .WriteTo.Async(c => c.File("Logs/logs.txt")) + .WriteTo.Async(c => c.Console()) + .CreateLogger(); + + await CreateHostBuilder(args).RunConsoleAsync(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .AddAppSettingsSecretsJson() + .ConfigureLogging((context, logging) => logging.ClearProviders()) + .ConfigureServices((hostContext, services) => { services.AddHostedService(); }); +} \ No newline at end of file diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/appsettings.json b/shared/EShopOnAbp.Keycloak.DbMigrator/appsettings.json new file mode 100644 index 00000000..077404aa --- /dev/null +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/appsettings.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file From 6eaba848a3b136999f27a3d03988211bba117264 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Wed, 19 Oct 2022 18:25:36 -0400 Subject: [PATCH 06/16] Updated keycloak migrator --- .../DbMigratorHostedService.cs | 1 - .../EShopOnAbp.Keycloak.DbMigrator.csproj | 3 +- .../EShopOnAbpDbMigratorModule.cs | 16 ++-- .../KeyCloakDataSeeder.cs | 21 ----- .../Keycloak/KeycloakClientOptions.cs | 6 -- .../Keycloak/KeycloakService.cs | 94 ------------------- .../Keycloak/Models/AccessTokenResult.cs | 16 ---- .../KeycloakClientOptions.cs | 9 ++ .../KeycloakDataSeeder.cs | 90 ++++++++++++++++++ .../appsettings.json | 7 +- 10 files changed, 116 insertions(+), 147 deletions(-) delete mode 100644 shared/EShopOnAbp.Keycloak.DbMigrator/KeyCloakDataSeeder.cs delete mode 100644 shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/KeycloakClientOptions.cs delete mode 100644 shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/KeycloakService.cs delete mode 100644 shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/Models/AccessTokenResult.cs create mode 100644 shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakClientOptions.cs create mode 100644 shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/DbMigratorHostedService.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/DbMigratorHostedService.cs index ce020de4..d28e0ecf 100644 --- a/shared/EShopOnAbp.Keycloak.DbMigrator/DbMigratorHostedService.cs +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/DbMigratorHostedService.cs @@ -1,6 +1,5 @@ using System.Threading; using System.Threading.Tasks; -using EShopOnAbp.DbMigrator.Keycloak; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj b/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj index 98834c63..a1d4a9e3 100644 --- a/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj @@ -8,8 +8,7 @@ - - + diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbpDbMigratorModule.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbpDbMigratorModule.cs index f739f1e6..fcdb8df2 100644 --- a/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbpDbMigratorModule.cs +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbpDbMigratorModule.cs @@ -1,5 +1,4 @@ -using EShopOnAbp.DbMigrator.Keycloak; -using EShopOnAbp.Shared.Hosting; +using EShopOnAbp.Shared.Hosting; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Modularity; @@ -13,9 +12,14 @@ public class EShopOnAbpDbMigratorModule : AbpModule public override void ConfigureServices(ServiceConfigurationContext context) { var configuration = context.Services.GetConfiguration(); - - context.Services.AddHttpClient(KeycloakService.HttpClientName); - - Configure(configuration); + + Configure(options => + { + options.Url = configuration["Keycloak:url"]; + options.AdminUserName = configuration["Keycloak:adminUsername"]; + options.AdminPassword = configuration["Keycloak:adminPassword"]; + options.RealmName = configuration["Keycloak:realmName"]; + } + ); } } \ No newline at end of file diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/KeyCloakDataSeeder.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/KeyCloakDataSeeder.cs deleted file mode 100644 index abd7dcfa..00000000 --- a/shared/EShopOnAbp.Keycloak.DbMigrator/KeyCloakDataSeeder.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Threading.Tasks; -using EShopOnAbp.DbMigrator.Keycloak; -using Volo.Abp.Data; -using Volo.Abp.DependencyInjection; - -namespace EShopOnAbp.DbMigrator; - -public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency -{ - private readonly KeycloakService _keycloakService; - - public KeyCloakDataSeeder(KeycloakService keycloakService) - { - _keycloakService = keycloakService; - } - - public async Task SeedAsync(DataSeedContext context) - { - var result = await _keycloakService.GetAdminAccessTokenAsync("master"); - } -} \ No newline at end of file diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/KeycloakClientOptions.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/KeycloakClientOptions.cs deleted file mode 100644 index 417638d7..00000000 --- a/shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/KeycloakClientOptions.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace EShopOnAbp.DbMigrator.Keycloak; - -public class KeycloakClientOptions -{ - -} \ No newline at end of file diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/KeycloakService.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/KeycloakService.cs deleted file mode 100644 index 7a8c9a70..00000000 --- a/shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/KeycloakService.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Net.Http.Json; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Threading.Tasks; -using EShopOnAbp.DbMigrator.Keycloak.Models; -using Microsoft.Extensions.Logging; -using Volo.Abp.DependencyInjection; - -namespace EShopOnAbp.DbMigrator.Keycloak; - -public class KeycloakService : ITransientDependency -{ - public const string HttpClientName = "KeycloakServiceHttpClientName"; - - // TODO: Option - public const string BaseUrl = "http://localhost:8080/admin/realms"; - public const string AdminClientId = "admin-cli"; - public const string AdminUserName = "admin"; - public const string AdminPassword = "1q2w3E*"; - - private readonly ILogger _logger; - private readonly IHttpClientFactory _clientFactory; - - public KeycloakService(IHttpClientFactory clientFactory, ILogger logger) - { - _clientFactory = clientFactory; - _logger = logger; - } - - // public async Task CreateClientAsync(string realm, Client client) - // { - // HttpResponseMessage response = await InternalCreateClientAsync(realm, client).ConfigureAwait(false); - // - // var locationPathAndQuery = response.Headers.Location.PathAndQuery; - // var clientId = response.IsSuccessStatusCode - // ? locationPathAndQuery.Substring(locationPathAndQuery.LastIndexOf("/", StringComparison.Ordinal) + 1) - // : null; - // return clientId; - // } - - private HttpClient CreateKeycloakApiHttpClient(string realm) - { - var httpClient = _clientFactory.CreateClient(HttpClientName); - httpClient.BaseAddress = new Uri(BaseUrl); - - return httpClient; - } - - private async Task CreateKeycloakApiHttpClientAsync(string realm, string token = null) - { - var httpClient = CreateKeycloakApiHttpClient(realm); - httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); - if (!string.IsNullOrEmpty(token)) - { - var accessToken = await GetAdminAccessTokenAsync(realm); - httpClient.DefaultRequestHeaders.Authorization = - new System.Net.Http.Headers.AuthenticationHeaderValue($"Bearer", $"{accessToken}"); - } - - return httpClient; - } - - public async Task GetAdminAccessTokenAsync(string realm) - { - var httpClient = _clientFactory.CreateClient(HttpClientName); - httpClient.BaseAddress = new Uri(BaseUrl); - - var result = string.Empty; - - var formContent = new FormUrlEncodedContent(new[] - { - new KeyValuePair("client_id", AdminClientId), - new KeyValuePair("username", AdminUserName), - new KeyValuePair("password", AdminPassword), - new KeyValuePair("grant_type", "password") - }); - - var httpResponseMessage = - await httpClient.PostAsync($"/realms/{realm}/protocol/openid-connect/token", formContent); - - if (httpResponseMessage.IsSuccessStatusCode) - { - var response = await httpResponseMessage.Content.ReadFromJsonAsync(); - result = response?.AccessToken; - } - - return result; - } -} \ No newline at end of file diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/Models/AccessTokenResult.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/Models/AccessTokenResult.cs deleted file mode 100644 index 83d2266b..00000000 --- a/shared/EShopOnAbp.Keycloak.DbMigrator/Keycloak/Models/AccessTokenResult.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Text.Json.Serialization; - -namespace EShopOnAbp.DbMigrator.Keycloak.Models; - -public class AccessTokenResult -{ - [JsonPropertyName("access_token")] public string AccessToken { get; set; } - [JsonPropertyName("expires_in")] public int Expiration { get; set; } - [JsonPropertyName("refresh_token")] public string RefreshToken { get; set; } - - [JsonPropertyName("refresh_expires_in")] - public int RefreshExpiration { get; set; } - - [JsonPropertyName("token_type")] public string TokenType { get; set; } - [JsonPropertyName("scope")] public string Scope { get; set; } -} \ No newline at end of file diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakClientOptions.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakClientOptions.cs new file mode 100644 index 00000000..6c24a9b8 --- /dev/null +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakClientOptions.cs @@ -0,0 +1,9 @@ +namespace EShopOnAbp.DbMigrator; + +public class KeycloakClientOptions +{ + public string Url { get; set; } + public string AdminUserName { get; set; } + public string AdminPassword { get; set; } + public string RealmName { get; set; } +} \ No newline at end of file diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs new file mode 100644 index 00000000..cda47f15 --- /dev/null +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Keycloak.Net; +using Keycloak.Net.Models.Clients; +using Microsoft.Extensions.Options; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.DbMigrator; + +public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency +{ + private readonly KeycloakClient _keycloakClient; + private readonly KeycloakClientOptions _keycloakOptions; + + public KeyCloakDataSeeder(IOptions keycloakClientOptions) + { + _keycloakOptions = keycloakClientOptions.Value; + + _keycloakClient = new KeycloakClient( + _keycloakOptions.Url, + _keycloakOptions.AdminUserName, + _keycloakOptions.AdminPassword + ); + } + + public async Task SeedAsync(DataSeedContext context) + { + await UpdateAdminUserAsync(); + await CreateClientsAsync(); + } + + private async Task CreateClientsAsync() + { + await CreatePublicWebClientAsync(); + } + + private async Task CreatePublicWebClientAsync() + { + var publicWebClient = (await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName, clientId: "PublicWeb")) + .FirstOrDefault(); + + if (publicWebClient == null) + { + publicWebClient = new Client() + { + ClientId = "PublicWeb", + Name = "Public Web Application", + Protocol = "openid-connect", + Enabled = true, + BaseUrl = "https://localhost:44335/", + RedirectUris = new List + { + "https://localhost:44335/signin-oidc" + }, + FrontChannelLogout = true, + PublicClient = true + }; + publicWebClient.Attributes = new Dictionary + { + { "post.logout.redirect.uris", "https://localhost:44335/signout-callback-oidc" } + }; + + await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, publicWebClient); + } + + } + + private async Task UpdateAdminUserAsync() + { + var users = await _keycloakClient.GetUsersAsync(_keycloakOptions.RealmName, username: "admin"); + var adminUser = users.FirstOrDefault(); + if (adminUser == null) + { + throw new Exception( + "Keycloak admin user is not provided, check if KEYCLOAK_ADMIN environment variable is passed properly."); + } + + if (string.IsNullOrEmpty(adminUser.Email)) + { + adminUser.Email = "admin@abp.io"; + adminUser.FirstName = "admin"; + adminUser.EmailVerified = true; + + await _keycloakClient.UpdateUserAsync(_keycloakOptions.RealmName, adminUser.Id, adminUser); + } + } +} \ No newline at end of file diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/appsettings.json b/shared/EShopOnAbp.Keycloak.DbMigrator/appsettings.json index 077404aa..2da323d1 100644 --- a/shared/EShopOnAbp.Keycloak.DbMigrator/appsettings.json +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/appsettings.json @@ -1,3 +1,8 @@ { - + "Keycloak": { + "url": "http://localhost:8080", + "adminUsername": "admin", + "adminPassword": "1q2w3E*", + "realmName": "master" + } } \ No newline at end of file From 25c672a46802c3df75de4ad901fc6b89a4ae0be8 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Thu, 20 Oct 2022 02:43:02 -0400 Subject: [PATCH 07/16] updated public web application --- .../EShopOnAbpPublicWebModule.cs | 26 ++++--------------- .../src/EShopOnAbp.PublicWeb/appsettings.json | 6 ++--- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs b/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs index a0aec3a1..8322f836 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs @@ -143,27 +143,10 @@ public class EShopOnAbpPublicWebModule : AbpModule .AddCookie("Cookies", options => { options.ExpireTimeSpan = TimeSpan.FromDays(365); }) .AddAbpOpenIdConnect("oidc", options => { - /* - * ASP.NET core uses the http://*:5000 and https://*:5001 ports for default communication with the OIDC middleware - * The app requires load balancing services to work with :80 or :443 - * These needs to be added to the keycloak client, in order for the redirect to work. - * If you however intend to use the app by itself then, - * Change the ports in launchsettings.json, but beware to also change the options.CallbackPath and options.SignedOutCallbackPath! - * Use LB services whenever possible, to reduce the config hazzle :) - */ - - //Use default signin scheme - // options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; - //Keycloak server - options.Authority = configuration["Keycloak:ServerRealm"]; - //Keycloak client ID + options.Authority = configuration["AuthServer:Authority"]; options.ClientId = configuration["Keycloak:ClientId"]; - //Keycloak client secret - // options.ClientSecret = configuration["Keycloak:ClientSecret"]; - //Keycloak .wellknown config origin to fetch config - options.MetadataAddress = configuration["Keycloak:Metadata"]; - //Require keycloak to use SSL - options.RequireHttpsMetadata = false; + options.MetadataAddress = configuration["AuthServer:MetaAddress"]; + options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); options.GetClaimsFromUserInfoEndpoint = true; options.Scope.Add("openid"); options.Scope.Add("profile"); @@ -171,7 +154,8 @@ public class EShopOnAbpPublicWebModule : AbpModule options.Scope.Add("phone"); options.Scope.Add("roles"); options.Scope.Add("offline_access"); - //Save the token + // options.Scope.Add("AdministrationService"); // Audiences couldn't be seeded -> outdated library + options.SaveTokens = true; //Token response type, will sometimes need to be changed to IdToken, depending on config. options.ResponseType = OpenIdConnectResponseType.Code; diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json b/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json index e82d3b47..4f8d0a7f 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json @@ -36,12 +36,12 @@ "Url": "http://localhost:9200" }, "AuthServer": { - "Authority": "https://localhost:44330", - "RequireHttpsMetadata": "true", + "Authority": "http://localhost:8080/realms/master", + "RequireHttpsMetadata": "false", "ClientId": "PublicWeb", "ClientSecret": "1q2w3e*", "IsOnProd": "false", - "MetaAddress": "https://localhost:44330" + "MetaAddress": "http://localhost:8080/realms/master/.well-known/openid-configuration" }, "Keycloak": { "ServerRealm": "http://localhost:8080/realms/master", From 8d6c94726b16caa63d2e365ea5ce19c17c56afec Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Thu, 20 Oct 2022 02:43:34 -0400 Subject: [PATCH 08/16] updated service authority configurations --- services/basket/src/EShopOnAbp.BasketService/appsettings.json | 4 ++-- .../EShopOnAbp.CatalogService.HttpApi.Host/appsettings.json | 4 ++-- .../EShopOnAbp.IdentityService.HttpApi.Host/appsettings.json | 4 ++-- .../EShopOnAbp.OrderingService.HttpApi.Host/appsettings.json | 4 ++-- .../EShopOnAbp.PaymentService.HttpApi.Host/appsettings.json | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/services/basket/src/EShopOnAbp.BasketService/appsettings.json b/services/basket/src/EShopOnAbp.BasketService/appsettings.json index 93adbecc..983d031c 100644 --- a/services/basket/src/EShopOnAbp.BasketService/appsettings.json +++ b/services/basket/src/EShopOnAbp.BasketService/appsettings.json @@ -4,8 +4,8 @@ "CorsOrigins": "https://localhost:44372,https://localhost:44373,https://localhost:44335" }, "AuthServer": { - "Authority": "https://localhost:44330", - "RequireHttpsMetadata": "true", + "Authority": "http://localhost:8080/realms/master", + "RequireHttpsMetadata": "false", "SwaggerClientId": "WebGateway_Swagger", "SwaggerClientSecret": "1q2w3e*" }, diff --git a/services/catalog/src/EShopOnAbp.CatalogService.HttpApi.Host/appsettings.json b/services/catalog/src/EShopOnAbp.CatalogService.HttpApi.Host/appsettings.json index 645ce941..2a8273b1 100644 --- a/services/catalog/src/EShopOnAbp.CatalogService.HttpApi.Host/appsettings.json +++ b/services/catalog/src/EShopOnAbp.CatalogService.HttpApi.Host/appsettings.json @@ -4,8 +4,8 @@ "CorsOrigins": "https://localhost:44372,https://localhost:44373,https://localhost:44335,http://localhost:4200" }, "AuthServer": { - "Authority": "https://localhost:44330", - "RequireHttpsMetadata": "true", + "Authority": "http://localhost:8080/realms/master", + "RequireHttpsMetadata": "false", "SwaggerClientId": "WebGateway_Swagger", "SwaggerClientSecret": "1q2w3e*" }, diff --git a/services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/appsettings.json b/services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/appsettings.json index 9678ab64..014033f8 100644 --- a/services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/appsettings.json +++ b/services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/appsettings.json @@ -4,8 +4,8 @@ "CorsOrigins": "https://localhost:44372,https://localhost:44373" }, "AuthServer": { - "Authority": "https://localhost:44330", - "RequireHttpsMetadata": "true", + "Authority": "http://localhost:8080/realms/master", + "RequireHttpsMetadata": "false", "SwaggerClientId": "WebGateway_Swagger", "SwaggerClientSecret": "1q2w3e*" }, diff --git a/services/ordering/src/EShopOnAbp.OrderingService.HttpApi.Host/appsettings.json b/services/ordering/src/EShopOnAbp.OrderingService.HttpApi.Host/appsettings.json index 5d6b54e1..865a6130 100644 --- a/services/ordering/src/EShopOnAbp.OrderingService.HttpApi.Host/appsettings.json +++ b/services/ordering/src/EShopOnAbp.OrderingService.HttpApi.Host/appsettings.json @@ -4,8 +4,8 @@ "CorsOrigins": "https://localhost:44372,https://localhost:44373,http://localhost:4200" }, "AuthServer": { - "Authority": "https://localhost:44330", - "RequireHttpsMetadata": "true", + "Authority": "http://localhost:8080/realms/master", + "RequireHttpsMetadata": "false", "SwaggerClientId": "WebGateway_Swagger", "SwaggerClientSecret": "1q2w3e*" }, diff --git a/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/appsettings.json b/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/appsettings.json index 83d05c81..93582be1 100644 --- a/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/appsettings.json +++ b/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/appsettings.json @@ -4,8 +4,8 @@ "CorsOrigins": "https://localhost:44372,https://localhost:44373" }, "AuthServer": { - "Authority": "https://localhost:44330", - "RequireHttpsMetadata": "true", + "Authority": "http://localhost:8080/realms/master", + "RequireHttpsMetadata": "false", "SwaggerClientId": "WebGateway_Swagger", "SwaggerClientSecret": "1q2w3e*" }, From 3849732fd40c47843bbd24de5407f7fc3324a9cf Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Thu, 20 Oct 2022 02:45:48 -0400 Subject: [PATCH 09/16] Disabled audience validation for microservices --- .../EShopOnAbpSharedHostingMicroservicesModule.cs | 1 + .../JwtBearerConfigurationHelper.cs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/shared/EShopOnAbp.Shared.Hosting.Microservices/EShopOnAbpSharedHostingMicroservicesModule.cs b/shared/EShopOnAbp.Shared.Hosting.Microservices/EShopOnAbpSharedHostingMicroservicesModule.cs index e497d6d5..fdad69fd 100644 --- a/shared/EShopOnAbp.Shared.Hosting.Microservices/EShopOnAbpSharedHostingMicroservicesModule.cs +++ b/shared/EShopOnAbp.Shared.Hosting.Microservices/EShopOnAbpSharedHostingMicroservicesModule.cs @@ -29,6 +29,7 @@ public class EShopOnAbpSharedHostingMicroservicesModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { + Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true; var configuration = context.Services.GetConfiguration(); Configure(options => diff --git a/shared/EShopOnAbp.Shared.Hosting.Microservices/JwtBearerConfigurationHelper.cs b/shared/EShopOnAbp.Shared.Hosting.Microservices/JwtBearerConfigurationHelper.cs index b00229f1..0cc48603 100644 --- a/shared/EShopOnAbp.Shared.Hosting.Microservices/JwtBearerConfigurationHelper.cs +++ b/shared/EShopOnAbp.Shared.Hosting.Microservices/JwtBearerConfigurationHelper.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.DependencyInjection; using System; +using Microsoft.IdentityModel.Tokens; using Volo.Abp.Modularity; namespace EShopOnAbp.Shared.Hosting.Microservices; @@ -19,6 +20,10 @@ public static class JwtBearerConfigurationHelper options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); options.Audience = audience; + options.TokenValidationParameters = new TokenValidationParameters() + { + ValidateAudience = false //Disabled since seeding audience is not possible with the current keycloak.net library version + }; }); } } \ No newline at end of file From 50857b2401791142626e76e74e4f5a56c05a25a0 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Thu, 20 Oct 2022 02:47:12 -0400 Subject: [PATCH 10/16] Added client scope creation --- .../KeycloakDataSeeder.cs | 61 ++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs index cda47f15..46b93c22 100644 --- a/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Threading.Tasks; using Keycloak.Net; using Keycloak.Net.Models.Clients; +using Keycloak.Net.Models.ClientScopes; +using Keycloak.Net.Models.ProtocolMappers; using Microsoft.Extensions.Options; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; @@ -29,9 +31,65 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency public async Task SeedAsync(DataSeedContext context) { await UpdateAdminUserAsync(); + await CreateClientScopesAsync(); await CreateClientsAsync(); } + private async Task CreateClientScopesAsync() + { + await CreateScopeAsync("AdministrationService"); + await CreateScopeAsync("IdentityService"); + await CreateScopeAsync("BasketService"); + await CreateScopeAsync("CatalogService"); + await CreateScopeAsync("OrderingService"); + await CreateScopeAsync("PaymentService"); + await CreateScopeAsync("CmskitService"); + } + + private async Task CreateScopeAsync(string scopeName) + { + var scope = (await _keycloakClient.GetClientScopesAsync(_keycloakOptions.RealmName)) + .FirstOrDefault(q => q.Name == scopeName); + + if (scope == null) + { + scope = new ClientScope() + { + Name = scopeName, + Description = scopeName + " scope", + Protocol = "openid-connect", + Attributes = new Attributes + { + ConsentScreenText = scopeName, + DisplayOnConsentScreen = "true", + IncludeInTokenScope = "true" + }, + ProtocolMappers = new List() + { + new ProtocolMapper() + { + Name = scopeName, + Protocol = "openid-connect", + _ProtocolMapper = "oidc-audience-mapper", + // Config = new Dictionary() + // { + // {"id.token.claim", "false"}, + // {"access.token.claim", "true"}, + // {"included.custom.audience", scopeName} + // } + Config = new Config() // This should be dictionary -> Outdated library + { + AccessTokenClaim = "true", + IdTokenClaim = "false" + } + } + } + }; + + await _keycloakClient.CreateClientScopeAsync(_keycloakOptions.RealmName, scope); + } + } + private async Task CreateClientsAsync() { await CreatePublicWebClientAsync(); @@ -62,10 +120,9 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency { { "post.logout.redirect.uris", "https://localhost:44335/signout-callback-oidc" } }; - + await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, publicWebClient); } - } private async Task UpdateAdminUserAsync() From c79dfa3a4e24145d9e184efdb8ece5d8affcd8b2 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Thu, 20 Oct 2022 23:20:05 -0400 Subject: [PATCH 11/16] updated appsettings configuration --- .../EShopOnAbpPublicWebModule.cs | 34 +++++-------------- .../src/EShopOnAbp.PublicWeb/appsettings.json | 1 - 2 files changed, 9 insertions(+), 26 deletions(-) diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs b/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs index 8322f836..69deb0fb 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs @@ -144,7 +144,7 @@ public class EShopOnAbpPublicWebModule : AbpModule .AddAbpOpenIdConnect("oidc", options => { options.Authority = configuration["AuthServer:Authority"]; - options.ClientId = configuration["Keycloak:ClientId"]; + options.ClientId = configuration["AuthServer:ClientId"]; options.MetadataAddress = configuration["AuthServer:MetaAddress"]; options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); options.GetClaimsFromUserInfoEndpoint = true; @@ -154,7 +154,14 @@ public class EShopOnAbpPublicWebModule : AbpModule options.Scope.Add("phone"); options.Scope.Add("roles"); options.Scope.Add("offline_access"); - // options.Scope.Add("AdministrationService"); // Audiences couldn't be seeded -> outdated library + // Audiences couldn't be seeded -> TODO: Update when library is updated + // options.Scope.Add("AccountService"); + // options.Scope.Add("AdministrationService"); + // options.Scope.Add("BasketService"); + // options.Scope.Add("CatalogService"); + // options.Scope.Add("PaymentService"); + // options.Scope.Add("OrderingService"); + // options.Scope.Add("CmskitService"); options.SaveTokens = true; //Token response type, will sometimes need to be changed to IdToken, depending on config. @@ -177,29 +184,6 @@ public class EShopOnAbpPublicWebModule : AbpModule options.ClaimActions.RemoveDuplicate(AbpClaimTypes.UserName); } }); - // .AddAbpOpenIdConnect("oidc", options => - // { - // options.Authority = configuration["AuthServer:Authority"]; - // options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); - // options.ResponseType = OpenIdConnectResponseType.CodeIdToken; - // - // options.ClientId = configuration["AuthServer:ClientId"]; - // options.ClientSecret = configuration["AuthServer:ClientSecret"]; - // - // options.SaveTokens = true; - // options.GetClaimsFromUserInfoEndpoint = true; - // - // options.Scope.Add("role"); - // options.Scope.Add("email"); - // options.Scope.Add("phone"); - // options.Scope.Add("AccountService"); - // options.Scope.Add("AdministrationService"); - // options.Scope.Add("BasketService"); - // options.Scope.Add("CatalogService"); - // options.Scope.Add("PaymentService"); - // options.Scope.Add("OrderingService"); - // options.Scope.Add("CmskitService"); - // }); if (Convert.ToBoolean(configuration["AuthServer:IsOnProd"])) { context.Services.Configure("oidc", options => diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json b/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json index 4f8d0a7f..c339e14c 100644 --- a/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json +++ b/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json @@ -39,7 +39,6 @@ "Authority": "http://localhost:8080/realms/master", "RequireHttpsMetadata": "false", "ClientId": "PublicWeb", - "ClientSecret": "1q2w3e*", "IsOnProd": "false", "MetaAddress": "http://localhost:8080/realms/master/.well-known/openid-configuration" }, From 293543ad8f986957bdeffb629650dfeb0212d681 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Thu, 20 Oct 2022 23:21:07 -0400 Subject: [PATCH 12/16] Added swagger client and configurations --- .../AdministrationServiceHttpApiHostModule.cs | 2 +- .../appsettings.json | 3 +- .../KeycloakDataSeeder.cs | 130 +++++++++++++++--- .../appsettings.json | 38 +++++ 4 files changed, 154 insertions(+), 19 deletions(-) diff --git a/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/AdministrationServiceHttpApiHostModule.cs b/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/AdministrationServiceHttpApiHostModule.cs index 9b349ff8..7ecb1575 100644 --- a/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/AdministrationServiceHttpApiHostModule.cs +++ b/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/AdministrationServiceHttpApiHostModule.cs @@ -91,7 +91,7 @@ public class AdministrationServiceHttpApiHostModule : AbpModule var configuration = context.ServiceProvider.GetRequiredService(); options.SwaggerEndpoint("/swagger/v1/swagger.json", "Administration Service API"); options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]); - options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]); + // options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]); }); app.UseAbpSerilogEnrichers(); app.UseAuditing(); diff --git a/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/appsettings.json b/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/appsettings.json index 620ba213..9368fc53 100644 --- a/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/appsettings.json +++ b/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/appsettings.json @@ -6,8 +6,7 @@ "AuthServer": { "Authority": "http://localhost:8080/realms/master", "RequireHttpsMetadata": "false", - "SwaggerClientId": "WebGateway_Swagger", - "SwaggerClientSecret": "1q2w3e*" + "SwaggerClientId": "SwaggerClient" }, "RemoteServices": { "AbpIdentity": { diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs index 46b93c22..223178e8 100644 --- a/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs @@ -6,6 +6,8 @@ using Keycloak.Net; using Keycloak.Net.Models.Clients; using Keycloak.Net.Models.ClientScopes; using Keycloak.Net.Models.ProtocolMappers; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; @@ -16,9 +18,13 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency { private readonly KeycloakClient _keycloakClient; private readonly KeycloakClientOptions _keycloakOptions; + private readonly ILogger _logger; + private readonly IConfiguration _configuration; - public KeyCloakDataSeeder(IOptions keycloakClientOptions) + public KeyCloakDataSeeder(IOptions keycloakClientOptions, ILogger logger, IConfiguration configuration) { + _logger = logger; + _configuration = configuration; _keycloakOptions = keycloakClientOptions.Value; _keycloakClient = new KeycloakClient( @@ -37,6 +43,7 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency private async Task CreateClientScopesAsync() { + await CreateScopeAsync("AccountService"); await CreateScopeAsync("AdministrationService"); await CreateScopeAsync("IdentityService"); await CreateScopeAsync("BasketService"); @@ -50,10 +57,10 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency { var scope = (await _keycloakClient.GetClientScopesAsync(_keycloakOptions.RealmName)) .FirstOrDefault(q => q.Name == scopeName); - + if (scope == null) { - scope = new ClientScope() + scope = new ClientScope { Name = scopeName, Description = scopeName + " scope", @@ -71,17 +78,17 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency Name = scopeName, Protocol = "openid-connect", _ProtocolMapper = "oidc-audience-mapper", - // Config = new Dictionary() - // { - // {"id.token.claim", "false"}, - // {"access.token.claim", "true"}, - // {"included.custom.audience", scopeName} - // } - Config = new Config() // This should be dictionary -> Outdated library + Config = new Dictionary() { - AccessTokenClaim = "true", - IdTokenClaim = "false" + { "id.token.claim", "false" }, + { "access.token.claim", "true" }, + { "included.custom.audience", scopeName } } + // Config = new Config() // This should be dictionary -> Outdated library + // { + // AccessTokenClaim = "true", + // IdTokenClaim = "false" + // } } } }; @@ -93,6 +100,57 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency private async Task CreateClientsAsync() { await CreatePublicWebClientAsync(); + await CreateSwaggerClientAsync(); + } + + private async Task CreateSwaggerClientAsync() + { + var swaggerClient = (await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName, clientId: "SwaggerClient")) + .FirstOrDefault(); + + if (swaggerClient == null) + { + var webGatewaySwaggerRootUrl = _configuration[$"Clients:WebGateway:RootUrl"].TrimEnd('/'); + var publicWebGatewayRootUrl = _configuration[$"Clients:PublicWebGateway:RootUrl"].TrimEnd('/'); + var accountServiceRootUrl = _configuration[$"Clients:AccountService:RootUrl"].TrimEnd('/'); + var identityServiceRootUrl = _configuration[$"Clients:IdentityService:RootUrl"].TrimEnd('/'); + var administrationServiceRootUrl = _configuration[$"Clients:AdministrationService:RootUrl"].TrimEnd('/'); + var catalogServiceRootUrl = _configuration[$"Clients:CatalogService:RootUrl"].TrimEnd('/'); + var basketServiceRootUrl = _configuration[$"Clients:BasketService:RootUrl"].TrimEnd('/'); + var orderingServiceRootUrl = _configuration[$"Clients:OrderingService:RootUrl"].TrimEnd('/'); + var paymentServiceRootUrl = _configuration[$"Clients:PaymentService:RootUrl"].TrimEnd('/'); + var cmskitServiceRootUrl = _configuration[$"Clients:CmskitService:RootUrl"].TrimEnd('/'); + + swaggerClient = new Client + { + ClientId = "SwaggerClient", + Name = "Swagger Client Application", + Protocol = "openid-connect", + Enabled = true, + // BaseUrl = "https://localhost:44335/", + RedirectUris = new List + { + $"{webGatewaySwaggerRootUrl}/swagger/oauth2-redirect.html", // WebGateway redirect uri + $"{publicWebGatewayRootUrl}/swagger/oauth2-redirect.html", // PublicWebGateway redirect uri + $"{accountServiceRootUrl}/swagger/oauth2-redirect.html", // AccountService redirect uri + $"{identityServiceRootUrl}/swagger/oauth2-redirect.html", // IdentityService redirect uri + $"{administrationServiceRootUrl}/swagger/oauth2-redirect.html", // AdministrationService redirect uri + $"{catalogServiceRootUrl}/swagger/oauth2-redirect.html", // CatalogService redirect uri + $"{basketServiceRootUrl}/swagger/oauth2-redirect.html", // BasketService redirect uri + $"{orderingServiceRootUrl}/swagger/oauth2-redirect.html", // OrderingService redirect uri + $"{paymentServiceRootUrl}/swagger/oauth2-redirect.html", // PaymentService redirect uri + $"{cmskitServiceRootUrl}/swagger/oauth2-redirect.html" // CmskitService redirect uri + }, + FrontChannelLogout = true, + PublicClient = true + }; + swaggerClient.Attributes = new Dictionary + { + { "post.logout.redirect.uris", "https://localhost:44335/signout-callback-oidc" } + }; + + await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, swaggerClient); + } } private async Task CreatePublicWebClientAsync() @@ -102,27 +160,64 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency if (publicWebClient == null) { - publicWebClient = new Client() + var publicWebRootUrl = _configuration[$"Clients:PublicWeb:RootUrl"]; + publicWebClient = new Client { ClientId = "PublicWeb", Name = "Public Web Application", Protocol = "openid-connect", Enabled = true, - BaseUrl = "https://localhost:44335/", + BaseUrl = publicWebRootUrl, RedirectUris = new List { - "https://localhost:44335/signin-oidc" + $"{publicWebRootUrl.TrimEnd('/')}/signin-oidc" }, FrontChannelLogout = true, PublicClient = true }; publicWebClient.Attributes = new Dictionary { - { "post.logout.redirect.uris", "https://localhost:44335/signout-callback-oidc" } + { "post.logout.redirect.uris", $"{publicWebRootUrl.TrimEnd('/')}/signout-callback-oidc" } }; await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, publicWebClient); } + + await AddOptionalClientScopesAsync( + "PublicWeb", + new List + { + "AccountService", "AdministrationService", "IdentityService", "BasketService", "CatalogService", + "OrderingService", "PaymentService", "CmskitService" + } + ); + } + + private async Task AddOptionalClientScopesAsync(string clientName, List scopes) + { + var client = (await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName, clientId: clientName)) + .FirstOrDefault(); + if (client == null) + { + _logger.LogError($"Couldn't find {clientName}! Could not seed optional scopes!"); + return; + } + + var clientOptionalScopes = + (await _keycloakClient.GetOptionalClientScopesAsync(_keycloakOptions.RealmName, client.Id)).ToList(); + + var clientScopes = (await _keycloakClient.GetClientScopesAsync(_keycloakOptions.RealmName)).ToList(); + + foreach (var scope in scopes) + { + if (!clientOptionalScopes.Any(q => q.Name == scope)) + { + var serviceScope = clientScopes.First(q => q.Name == scope); + _logger.LogInformation($"Seeding {scope} scope to {clientName}."); + await _keycloakClient.UpdateOptionalClientScopeAsync(_keycloakOptions.RealmName, client.Id, + serviceScope.Id); + } + } } private async Task UpdateAdminUserAsync() @@ -131,6 +226,8 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency var adminUser = users.FirstOrDefault(); if (adminUser == null) { + _logger.LogError( + "Keycloak admin user is not provided, check if KEYCLOAK_ADMIN environment variable is passed properly."); throw new Exception( "Keycloak admin user is not provided, check if KEYCLOAK_ADMIN environment variable is passed properly."); } @@ -141,6 +238,7 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency adminUser.FirstName = "admin"; adminUser.EmailVerified = true; + _logger.LogInformation("Updating admin user with email and first name..."); await _keycloakClient.UpdateUserAsync(_keycloakOptions.RealmName, adminUser.Id, adminUser); } } diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/appsettings.json b/shared/EShopOnAbp.Keycloak.DbMigrator/appsettings.json index 2da323d1..13bcec2d 100644 --- a/shared/EShopOnAbp.Keycloak.DbMigrator/appsettings.json +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/appsettings.json @@ -4,5 +4,43 @@ "adminUsername": "admin", "adminPassword": "1q2w3E*", "realmName": "master" + }, + "Clients": { + "Web": { + "RootUrl": "http://localhost:4200" + }, + "PublicWeb": { + "RootUrl": "https://localhost:44335" + }, + "WebGateway": { + "RootUrl": "https://localhost:44372" + }, + "PublicWebGateway": { + "RootUrl": "https://localhost:44373" + }, + "AccountService": { + "RootUrl": "https://localhost:44330" + }, + "IdentityService": { + "RootUrl": "https://localhost:44351" + }, + "AdministrationService": { + "RootUrl": "https://localhost:44353" + }, + "CatalogService": { + "RootUrl": "https://localhost:44354" + }, + "BasketService": { + "RootUrl": "https://localhost:44355" + }, + "OrderingService": { + "RootUrl": "https://localhost:44356" + }, + "PaymentService": { + "RootUrl": "https://localhost:44357" + }, + "CmskitService": { + "RootUrl": "https://localhost:44358" + } } } \ No newline at end of file From 3ef362f7f249326c0379348de36e089eae5078d5 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Fri, 21 Oct 2022 23:08:15 -0400 Subject: [PATCH 13/16] Added angular client --- apps/angular/src/environments/environment.ts | 5 +- .../KeycloakDataSeeder.cs | 66 ++++++++++++++----- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/apps/angular/src/environments/environment.ts b/apps/angular/src/environments/environment.ts index 9c3c768d..243cea14 100644 --- a/apps/angular/src/environments/environment.ts +++ b/apps/angular/src/environments/environment.ts @@ -9,11 +9,12 @@ export const environment = { name: 'EShopOnAbp', }, oAuthConfig: { - issuer: 'https://localhost:44330', + issuer: 'http://localhost:8080/realms/master', redirectUri: baseUrl, clientId: 'Web', responseType: 'code', - scope: 'offline_access openid profile email phone AccountService IdentityService AdministrationService CatalogService OrderingService', + scope: 'offline_access openid profile email phone', + // scope: 'offline_access openid profile email phone AccountService IdentityService AdministrationService CatalogService OrderingService', //TODO: Update when https://github.com/AnderssonPeter/Keycloak.Net/pull/5 is merged //requireHttps: true, }, apis: { diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs index 223178e8..fbc82386 100644 --- a/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs @@ -100,7 +100,48 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency private async Task CreateClientsAsync() { await CreatePublicWebClientAsync(); - await CreateSwaggerClientAsync(); + await CreateSwaggerClientAsync(); // TODO: Test when Volo.Abp.Swashbuckle v6.0.1 is released (https://github.com/abpframework/abp/pull/14409) + await CreateWebClientAsync(); + } + + private async Task CreateWebClientAsync() + { + var webClient = (await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName, clientId: "Web")) + .FirstOrDefault(); + + if (webClient == null) + { + var webRootUrl = _configuration[$"Clients:Web:RootUrl"]; + webClient = new Client + { + ClientId = "Web", + Name = "Angular Back-Office Web Application", + Protocol = "openid-connect", + Enabled = true, + BaseUrl = webRootUrl, + RedirectUris = new List + { + $"{webRootUrl.TrimEnd('/')}" + }, + FrontChannelLogout = true, + PublicClient = true + }; + webClient.Attributes = new Dictionary + { + { "post.logout.redirect.uris", $"{webRootUrl.TrimEnd('/')}" } + }; + + await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, webClient); + //TODO: Update when //https://github.com/AnderssonPeter/Keycloak.Net/pull/5 is merged + // await AddOptionalClientScopesAsync( + // "PublicWeb", + // new List + // { + // "AccountService", "AdministrationService", "IdentityService", "BasketService", "CatalogService", + // "OrderingService", "PaymentService", "CmskitService" + // } + // ); + } } private async Task CreateSwaggerClientAsync() @@ -127,7 +168,6 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency Name = "Swagger Client Application", Protocol = "openid-connect", Enabled = true, - // BaseUrl = "https://localhost:44335/", RedirectUris = new List { $"{webGatewaySwaggerRootUrl}/swagger/oauth2-redirect.html", // WebGateway redirect uri @@ -144,10 +184,6 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency FrontChannelLogout = true, PublicClient = true }; - swaggerClient.Attributes = new Dictionary - { - { "post.logout.redirect.uris", "https://localhost:44335/signout-callback-oidc" } - }; await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, swaggerClient); } @@ -181,16 +217,16 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency }; await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, publicWebClient); + //TODO: Update when //https://github.com/AnderssonPeter/Keycloak.Net/pull/5 is merged + // await AddOptionalClientScopesAsync( + // "PublicWeb", + // new List + // { + // "AccountService", "AdministrationService", "IdentityService", "BasketService", "CatalogService", + // "OrderingService", "PaymentService", "CmskitService" + // } + // ); } - - await AddOptionalClientScopesAsync( - "PublicWeb", - new List - { - "AccountService", "AdministrationService", "IdentityService", "BasketService", "CatalogService", - "OrderingService", "PaymentService", "CmskitService" - } - ); } private async Task AddOptionalClientScopesAsync(string clientName, List scopes) From 8709d2598e12ff2712fb8cf048b4b2348fb1cfcf Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Fri, 21 Oct 2022 23:50:44 -0400 Subject: [PATCH 14/16] updated CmskitService configuration for keycloak --- .../EShopOnAbp.CmskitService.HttpApi.Host/appsettings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/cmskit/src/EShopOnAbp.CmskitService.HttpApi.Host/appsettings.json b/services/cmskit/src/EShopOnAbp.CmskitService.HttpApi.Host/appsettings.json index 151dd3ec..2558921d 100644 --- a/services/cmskit/src/EShopOnAbp.CmskitService.HttpApi.Host/appsettings.json +++ b/services/cmskit/src/EShopOnAbp.CmskitService.HttpApi.Host/appsettings.json @@ -4,8 +4,8 @@ "CorsOrigins": "https://localhost:44372,https://localhost:44373,http://localhost:4200,https://localhost:44335" }, "AuthServer": { - "Authority": "https://localhost:44330", - "RequireHttpsMetadata": "true", + "Authority": "http://localhost:8080/realms/master", + "RequireHttpsMetadata": "false", "SwaggerClientId": "WebGateway_Swagger", "SwaggerClientSecret": "1q2w3e*" }, From 7817404531d368073847e36e945551c059366f9f Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Fri, 21 Oct 2022 23:51:21 -0400 Subject: [PATCH 15/16] Updated keycloak data seeder for current keycloak library --- .../EShopOnAbp.Keycloak.DbMigrator.csproj | 1 + .../KeycloakDataSeeder.cs | 20 ++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj b/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj index a1d4a9e3..f7f9c1ba 100644 --- a/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj @@ -13,6 +13,7 @@ + diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs index fbc82386..a5d9428d 100644 --- a/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs @@ -78,17 +78,17 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency Name = scopeName, Protocol = "openid-connect", _ProtocolMapper = "oidc-audience-mapper", - Config = new Dictionary() - { - { "id.token.claim", "false" }, - { "access.token.claim", "true" }, - { "included.custom.audience", scopeName } - } - // Config = new Config() // This should be dictionary -> Outdated library + // Config = new Dictionary() //TODO: Update when //https://github.com/AnderssonPeter/Keycloak.Net/pull/5 is merged // { - // AccessTokenClaim = "true", - // IdTokenClaim = "false" + // { "id.token.claim", "false" }, + // { "access.token.claim", "true" }, + // { "included.custom.audience", scopeName } // } + Config = new Config() // This should be dictionary -> Outdated library + { + AccessTokenClaim = "true", + IdTokenClaim = "false" + } } } }; @@ -132,6 +132,7 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency }; await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, webClient); + //TODO: Update when //https://github.com/AnderssonPeter/Keycloak.Net/pull/5 is merged // await AddOptionalClientScopesAsync( // "PublicWeb", @@ -217,6 +218,7 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency }; await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, publicWebClient); + //TODO: Update when //https://github.com/AnderssonPeter/Keycloak.Net/pull/5 is merged // await AddOptionalClientScopesAsync( // "PublicWeb", From 8cbf311b43e11c6627c82e531bfad709da78e7fb Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Sat, 22 Oct 2022 00:06:05 -0400 Subject: [PATCH 16/16] removed AccountService scope seed --- shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs index a5d9428d..32aff192 100644 --- a/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs +++ b/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs @@ -133,12 +133,12 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, webClient); - //TODO: Update when //https://github.com/AnderssonPeter/Keycloak.Net/pull/5 is merged + //TODO: Update when //https://github.com/AnderssonPeter/Keycloak.Net/pull/5 is merged // await AddOptionalClientScopesAsync( // "PublicWeb", // new List // { - // "AccountService", "AdministrationService", "IdentityService", "BasketService", "CatalogService", + // "AdministrationService", "IdentityService", "BasketService", "CatalogService", // "OrderingService", "PaymentService", "CmskitService" // } // ); @@ -224,7 +224,7 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency // "PublicWeb", // new List // { - // "AccountService", "AdministrationService", "IdentityService", "BasketService", "CatalogService", + // "AdministrationService", "IdentityService", "BasketService", "CatalogService", // "OrderingService", "PaymentService", "CmskitService" // } // );