Browse Source

Merge pull request #134 from abpframework/gterdem/keycloak_integration

KeyCloak Integration for eShopOnAbp project
gterdem/swagger_update
Enis Necipoglu 4 years ago
committed by GitHub
parent
commit
8cd05c8f49
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      EShopOnAbp.sln
  2. 5
      apps/angular/src/environments/environment.ts
  3. 52
      apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs
  4. 14
      apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json
  5. 19
      etc/docker/docker-compose.infrastructure.override.yml
  6. 15
      etc/docker/docker-compose.infrastructure.yml
  7. 2
      services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/AdministrationServiceHttpApiHostModule.cs
  8. 7
      services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/appsettings.json
  9. 4
      services/basket/src/EShopOnAbp.BasketService/appsettings.json
  10. 4
      services/catalog/src/EShopOnAbp.CatalogService.HttpApi.Host/appsettings.json
  11. 4
      services/cmskit/src/EShopOnAbp.CmskitService.HttpApi.Host/appsettings.json
  12. 4
      services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/appsettings.json
  13. 4
      services/ordering/src/EShopOnAbp.OrderingService.HttpApi.Host/appsettings.json
  14. 4
      services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/appsettings.json
  15. 47
      shared/EShopOnAbp.Keycloak.DbMigrator/DbMigratorHostedService.cs
  16. 34
      shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj
  17. 25
      shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbpDbMigratorModule.cs
  18. 9
      shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakClientOptions.cs
  19. 283
      shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs
  20. 30
      shared/EShopOnAbp.Keycloak.DbMigrator/MigrationService.cs
  21. 35
      shared/EShopOnAbp.Keycloak.DbMigrator/Program.cs
  22. 46
      shared/EShopOnAbp.Keycloak.DbMigrator/appsettings.json
  23. 1
      shared/EShopOnAbp.Shared.Hosting.Microservices/EShopOnAbpSharedHostingMicroservicesModule.cs
  24. 5
      shared/EShopOnAbp.Shared.Hosting.Microservices/JwtBearerConfigurationHelper.cs

7
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}

5
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: {

52
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;
@ -142,25 +144,45 @@ public class EShopOnAbpPublicWebModule : AbpModule
.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.MetadataAddress = configuration["AuthServer:MetaAddress"];
options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("role");
options.Scope.Add("openid");
options.Scope.Add("profile");
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("roles");
options.Scope.Add("offline_access");
// 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.
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
// };
if (AbpClaimTypes.UserName != "preferred_username")
{
options.ClaimActions.MapJsonKey(AbpClaimTypes.UserName, "preferred_username");
options.ClaimActions.DeleteClaim("preferred_username");
options.ClaimActions.RemoveDuplicate(AbpClaimTypes.UserName);
}
});
if (Convert.ToBoolean(configuration["AuthServer:IsOnProd"]))
{

14
apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json

@ -36,12 +36,18 @@
"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",
"Metadata": "http://localhost:8080/realms/master/.well-known/openid-configuration",
"ClientId": "PublicWeb",
"TokenExchange": "http://localhost:8080/realms/master/protocol/openid-connect/token",
"Audience": "some-audience"
},
"ReverseProxy": {
"Routes": {

19
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,19 @@ services:
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin}
keycloak:
ports:
- "8080:8080"
environment:
DB_VENDOR: postgres
DB_ADDR: "postgres-db"
DB_DATABASE: "keycloak"
DB_USER: "postgres"
DB_PASSWORD: "myPassw0rd"
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: "1q2w3E*"
KC_HEALTH_ENABLED: "true"
entrypoint: ["/opt/keycloak/bin/kc.sh", "start-dev"]

15
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: quay.io/keycloak/keycloak:19.0.2
depends_on:
- postgres-db
restart: unless-stopped
networks:
- eshoponabp-network
volumes:
postgres_data:

2
services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/AdministrationServiceHttpApiHostModule.cs

@ -91,7 +91,7 @@ public class AdministrationServiceHttpApiHostModule : AbpModule
var configuration = context.ServiceProvider.GetRequiredService<IConfiguration>();
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();

7
services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/appsettings.json

@ -4,10 +4,9 @@
"CorsOrigins": "https://localhost:44372,https://localhost:44373"
},
"AuthServer": {
"Authority": "https://localhost:44330",
"RequireHttpsMetadata": "true",
"SwaggerClientId": "WebGateway_Swagger",
"SwaggerClientSecret": "1q2w3e*"
"Authority": "http://localhost:8080/realms/master",
"RequireHttpsMetadata": "false",
"SwaggerClientId": "SwaggerClient"
},
"RemoteServices": {
"AbpIdentity": {

4
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*"
},

4
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*"
},

4
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*"
},

4
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*"
},

4
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*"
},

4
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*"
},

47
shared/EShopOnAbp.Keycloak.DbMigrator/DbMigratorHostedService.cs

@ -0,0 +1,47 @@
using System.Threading;
using System.Threading.Tasks;
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<EShopOnAbpDbMigratorModule>(options =>
{
options.Services.ReplaceConfiguration(_configuration);
options.UseAutofac();
options.Services.AddLogging(c => c.AddSerilog());
}))
{
application.Initialize();
await application
.ServiceProvider
.GetRequiredService<MigrationService>()
.MigrateAsync(cancellationToken);
application.Shutdown();
_hostApplicationLifetime.StopApplication();
}
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

34
shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>EShopOnAbp.DbMigrator</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<PackageReference Include="Keycloak.v19.Net" Version="1.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EShopOnAbp.Shared.Hosting\EShopOnAbp.Shared.Hosting.csproj" />
<!-- <ProjectReference Include="..\..\..\Keycloak.Net\src\Keycloak.Net.Core\Keycloak.Net.Core.csproj" />-->
</ItemGroup>
<ItemGroup>
<Compile Remove="Logs\**" />
<Content Remove="Logs\**" />
<EmbeddedResource Remove="Logs\**" />
<None Remove="Logs\**" />
</ItemGroup>
<ItemGroup>
<None Remove="appsettings.json" />
<Content Include="appsettings.json">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

25
shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbpDbMigratorModule.cs

@ -0,0 +1,25 @@
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();
Configure<KeycloakClientOptions>(options =>
{
options.Url = configuration["Keycloak:url"];
options.AdminUserName = configuration["Keycloak:adminUsername"];
options.AdminPassword = configuration["Keycloak:adminPassword"];
options.RealmName = configuration["Keycloak:realmName"];
}
);
}
}

9
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; }
}

283
shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs

@ -0,0 +1,283 @@
using System;
using System.Collections.Generic;
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.Configuration;
using Microsoft.Extensions.Logging;
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;
private readonly ILogger<KeyCloakDataSeeder> _logger;
private readonly IConfiguration _configuration;
public KeyCloakDataSeeder(IOptions<KeycloakClientOptions> keycloakClientOptions, ILogger<KeyCloakDataSeeder> logger, IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
_keycloakOptions = keycloakClientOptions.Value;
_keycloakClient = new KeycloakClient(
_keycloakOptions.Url,
_keycloakOptions.AdminUserName,
_keycloakOptions.AdminPassword
);
}
public async Task SeedAsync(DataSeedContext context)
{
await UpdateAdminUserAsync();
await CreateClientScopesAsync();
await CreateClientsAsync();
}
private async Task CreateClientScopesAsync()
{
await CreateScopeAsync("AccountService");
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<ProtocolMapper>()
{
new ProtocolMapper()
{
Name = scopeName,
Protocol = "openid-connect",
_ProtocolMapper = "oidc-audience-mapper",
// Config = new Dictionary<string, string>() //TODO: Update when //https://github.com/AnderssonPeter/Keycloak.Net/pull/5 is merged
// {
// { "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();
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<string>
{
$"{webRootUrl.TrimEnd('/')}"
},
FrontChannelLogout = true,
PublicClient = true
};
webClient.Attributes = new Dictionary<string, object>
{
{ "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<string>
// {
// "AdministrationService", "IdentityService", "BasketService", "CatalogService",
// "OrderingService", "PaymentService", "CmskitService"
// }
// );
}
}
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,
RedirectUris = new List<string>
{
$"{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
};
await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, swaggerClient);
}
}
private async Task CreatePublicWebClientAsync()
{
var publicWebClient = (await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName, clientId: "PublicWeb"))
.FirstOrDefault();
if (publicWebClient == null)
{
var publicWebRootUrl = _configuration[$"Clients:PublicWeb:RootUrl"];
publicWebClient = new Client
{
ClientId = "PublicWeb",
Name = "Public Web Application",
Protocol = "openid-connect",
Enabled = true,
BaseUrl = publicWebRootUrl,
RedirectUris = new List<string>
{
$"{publicWebRootUrl.TrimEnd('/')}/signin-oidc"
},
FrontChannelLogout = true,
PublicClient = true
};
publicWebClient.Attributes = new Dictionary<string, object>
{
{ "post.logout.redirect.uris", $"{publicWebRootUrl.TrimEnd('/')}/signout-callback-oidc" }
};
await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, publicWebClient);
//TODO: Update when //https://github.com/AnderssonPeter/Keycloak.Net/pull/5 is merged
// await AddOptionalClientScopesAsync(
// "PublicWeb",
// new List<string>
// {
// "AdministrationService", "IdentityService", "BasketService", "CatalogService",
// "OrderingService", "PaymentService", "CmskitService"
// }
// );
}
}
private async Task AddOptionalClientScopesAsync(string clientName, List<string> 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()
{
var users = await _keycloakClient.GetUsersAsync(_keycloakOptions.RealmName, username: "admin");
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.");
}
if (string.IsNullOrEmpty(adminUser.Email))
{
adminUser.Email = "admin@abp.io";
adminUser.FirstName = "admin";
adminUser.EmailVerified = true;
_logger.LogInformation("Updating admin user with email and first name...");
await _keycloakClient.UpdateUserAsync(_keycloakOptions.RealmName, adminUser.Id, adminUser);
}
}
}

30
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<MigrationService> _logger;
private readonly IDataSeeder _dataSeeder;
public MigrationService(ILogger<MigrationService> 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!");
}
}

35
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<DbMigratorHostedService>(); });
}

46
shared/EShopOnAbp.Keycloak.DbMigrator/appsettings.json

@ -0,0 +1,46 @@
{
"Keycloak": {
"url": "http://localhost:8080",
"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"
}
}
}

1
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<AbpMultiTenancyOptions>(options =>

5
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
};
});
}
}
Loading…
Cancel
Save