Browse Source

Merge pull request #136 from abpframework/gterdem/keycloak_enh

Keycloak enhancements
gterdem/swagger_update
Galip Tolga Erdem 4 years ago
committed by GitHub
parent
commit
055a750127
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      apps/angular/src/environments/environment.ts
  2. 37
      apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs
  3. 2
      services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/appsettings.json
  4. 2
      services/basket/src/EShopOnAbp.BasketService/appsettings.json
  5. 2
      services/catalog/src/EShopOnAbp.CatalogService.HttpApi.Host/appsettings.json
  6. 2
      services/cmskit/src/EShopOnAbp.CmskitService.HttpApi.Host/appsettings.json
  7. 2
      services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/IdentityServiceHttpApiHostModule.cs
  8. 2
      services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/appsettings.json
  9. 3
      shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj
  10. 104
      shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs
  11. 5
      shared/EShopOnAbp.Shared.Hosting.Microservices/JwtBearerConfigurationHelper.cs
  12. 16
      tye.yaml

3
apps/angular/src/environments/environment.ts

@ -13,8 +13,7 @@ export const environment = {
redirectUri: baseUrl,
clientId: 'Web',
responseType: 'code',
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
scope: 'offline_access openid profile email phone roles AdministrationService IdentityService BasketService CatalogService OrderingService PaymentService CmskitService',
//requireHttps: true,
},
apis: {

37
apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs

@ -72,8 +72,6 @@ namespace EShopOnAbp.PublicWeb;
typeof(CmskitServiceHttpApiClientModule),
typeof(CmsKitDomainModule),
typeof(CmsKitPublicWebModule)
)]
public class EShopOnAbpPublicWebModule : AbpModule
{
@ -147,6 +145,7 @@ public class EShopOnAbpPublicWebModule : AbpModule
options.ClientId = configuration["AuthServer:ClientId"];
options.MetadataAddress = configuration["AuthServer:MetaAddress"];
options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
@ -154,18 +153,16 @@ public class EShopOnAbpPublicWebModule : AbpModule
options.Scope.Add("phone");
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.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;
@ -176,7 +173,7 @@ public class EShopOnAbpPublicWebModule : AbpModule
// RoleClaimType = ClaimTypes.Role,
// ValidateIssuer = true
// };
if (AbpClaimTypes.UserName != "preferred_username")
{
options.ClaimActions.MapJsonKey(AbpClaimTypes.UserName, "preferred_username");
@ -195,18 +192,21 @@ public class EShopOnAbpPublicWebModule : AbpModule
options.Events.OnRedirectToIdentityProvider = async ctx =>
{
// Intercept the redirection so the browser navigates to the right URL in your host
ctx.ProtocolMessage.IssuerAddress = configuration["AuthServer:Authority"].EnsureEndsWith('/') + "connect/authorize";
ctx.ProtocolMessage.IssuerAddress = configuration["AuthServer:Authority"].EnsureEndsWith('/') +
"connect/authorize";
if (previousOnRedirectToIdentityProvider != null)
{
await previousOnRedirectToIdentityProvider(ctx);
}
};
var previousOnRedirectToIdentityProviderForSignOut = options.Events.OnRedirectToIdentityProviderForSignOut;
var previousOnRedirectToIdentityProviderForSignOut =
options.Events.OnRedirectToIdentityProviderForSignOut;
options.Events.OnRedirectToIdentityProviderForSignOut = async ctx =>
{
// Intercept the redirection for signout so the browser navigates to the right URL in your host
ctx.ProtocolMessage.IssuerAddress = configuration["AuthServer:Authority"].EnsureEndsWith('/') + "connect/endsession";
ctx.ProtocolMessage.IssuerAddress = configuration["AuthServer:Authority"].EnsureEndsWith('/') +
"connect/endsession";
if (previousOnRedirectToIdentityProviderForSignOut != null)
{
@ -251,7 +251,8 @@ public class EShopOnAbpPublicWebModule : AbpModule
private void ConfigureBasketHttpClient(ServiceConfigurationContext context)
{
context.Services.AddStaticHttpClientProxies(
typeof(BasketServiceContractsModule).Assembly, remoteServiceConfigurationName: BasketServiceConstants.RemoteServiceName
typeof(BasketServiceContractsModule).Assembly,
remoteServiceConfigurationName: BasketServiceConstants.RemoteServiceName
);
Configure<AbpVirtualFileSystemOptions>(options =>

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

@ -1,7 +1,7 @@
{
"App": {
"SelfUrl": "https://localhost:44353",
"CorsOrigins": "https://localhost:44372,https://localhost:44373"
"CorsOrigins": "https://localhost:44372,https://localhost:44373,http://localhost:4200"
},
"AuthServer": {
"Authority": "http://localhost:8080/realms/master",

2
services/basket/src/EShopOnAbp.BasketService/appsettings.json

@ -1,7 +1,7 @@
{
"App": {
"SelfUrl": "https://localhost:44355",
"CorsOrigins": "https://localhost:44372,https://localhost:44373,https://localhost:44335"
"CorsOrigins": "https://localhost:44372,https://localhost:44373,,http://localhost:4200"
},
"AuthServer": {
"Authority": "http://localhost:8080/realms/master",

2
services/catalog/src/EShopOnAbp.CatalogService.HttpApi.Host/appsettings.json

@ -1,7 +1,7 @@
{
"App": {
"SelfUrl": "https://localhost:44354",
"CorsOrigins": "https://localhost:44372,https://localhost:44373,https://localhost:44335,http://localhost:4200"
"CorsOrigins": "https://localhost:44372,https://localhost:44373,http://localhost:4200"
},
"AuthServer": {
"Authority": "http://localhost:8080/realms/master",

2
services/cmskit/src/EShopOnAbp.CmskitService.HttpApi.Host/appsettings.json

@ -1,7 +1,7 @@
{
"App": {
"SelfUrl": "https://localhost:44358",
"CorsOrigins": "https://localhost:44372,https://localhost:44373,http://localhost:4200,https://localhost:44335"
"CorsOrigins": "https://localhost:44372,https://localhost:44373,http://localhost:4200"
},
"AuthServer": {
"Authority": "http://localhost:8080/realms/master",

2
services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/IdentityServiceHttpApiHostModule.cs

@ -40,7 +40,7 @@ public class IdentityServiceHttpApiHostModule : AbpModule
{
{ "IdentityService", "Identity Service API" }
},
apiTitle: "IdentityService Gateway API"
apiTitle: "IdentityService API"
);

2
services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/appsettings.json

@ -1,7 +1,7 @@
{
"App": {
"SelfUrl": "https://localhost:44351",
"CorsOrigins": "https://localhost:44372,https://localhost:44373"
"CorsOrigins": "https://localhost:44372,https://localhost:44373,http://localhost:4200"
},
"AuthServer": {
"Authority": "http://localhost:8080/realms/master",

3
shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj

@ -8,12 +8,11 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<PackageReference Include="Keycloak.v19.Net" Version="1.0.0" />
<PackageReference Include="Keycloak.Net.Core" Version="1.0.20" />
</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>

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

@ -21,7 +21,8 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency
private readonly ILogger<KeyCloakDataSeeder> _logger;
private readonly IConfiguration _configuration;
public KeyCloakDataSeeder(IOptions<KeycloakClientOptions> keycloakClientOptions, ILogger<KeyCloakDataSeeder> logger, IConfiguration configuration)
public KeyCloakDataSeeder(IOptions<KeycloakClientOptions> keycloakClientOptions, ILogger<KeyCloakDataSeeder> logger,
IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
@ -37,13 +38,40 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency
public async Task SeedAsync(DataSeedContext context)
{
await UpdateAdminUserAsync();
await CreateRoleMapperAsync();
await CreateClientScopesAsync();
await CreateClientsAsync();
}
private async Task CreateRoleMapperAsync()
{
var roleScope = (await _keycloakClient.GetClientScopesAsync(_keycloakOptions.RealmName))
.FirstOrDefault(q => q.Name == "roles");
if (roleScope == null)
return;
if (!roleScope.ProtocolMappers.Any(q => q.Name == "roles"))
{
await _keycloakClient.CreateProtocolMapperAsync(_keycloakOptions.RealmName, roleScope.Id,
new ProtocolMapper()
{
Name = "roles",
Protocol = "openid-connect",
_ProtocolMapper = "oidc-usermodel-realm-role-mapper",
Config = new Dictionary<string, string>()
{
{ "access.token.claim", "true" },
{ "id.token.claim", "true" },
{ "claim.name", "roles" },
{ "multivalued", "true" },
{ "userinfo.token.claim", "true" },
}
});
}
}
private async Task CreateClientScopesAsync()
{
await CreateScopeAsync("AccountService");
await CreateScopeAsync("AdministrationService");
await CreateScopeAsync("IdentityService");
await CreateScopeAsync("BasketService");
@ -78,17 +106,15 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency
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"
}
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 }
}
}
}
};
@ -132,24 +158,24 @@ 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",
// new List<string>
// {
// "AdministrationService", "IdentityService", "BasketService", "CatalogService",
// "OrderingService", "PaymentService", "CmskitService"
// }
// );
await AddOptionalClientScopesAsync(
"Web",
new List<string>
{
"AdministrationService", "IdentityService", "BasketService", "CatalogService",
"OrderingService", "PaymentService", "CmskitService"
}
);
}
}
private async Task CreateSwaggerClientAsync()
{
var swaggerClient = (await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName, clientId: "SwaggerClient"))
var swaggerClient =
(await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName, clientId: "SwaggerClient"))
.FirstOrDefault();
if (swaggerClient == null)
{
var webGatewaySwaggerRootUrl = _configuration[$"Clients:WebGateway:RootUrl"].TrimEnd('/');
@ -162,7 +188,7 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency
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",
@ -204,13 +230,14 @@ public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency
Name = "Public Web Application",
Protocol = "openid-connect",
Enabled = true,
BaseUrl = publicWebRootUrl,
BaseUrl = publicWebRootUrl,
RedirectUris = new List<string>
{
$"{publicWebRootUrl.TrimEnd('/')}/signin-oidc"
},
FrontChannelLogout = true,
PublicClient = true
PublicClient = true,
ImplicitFlowEnabled = true // for hybrid flow
};
publicWebClient.Attributes = new Dictionary<string, object>
{
@ -218,16 +245,15 @@ 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<string>
// {
// "AdministrationService", "IdentityService", "BasketService", "CatalogService",
// "OrderingService", "PaymentService", "CmskitService"
// }
// );
await AddOptionalClientScopesAsync(
"PublicWeb",
new List<string>
{
"AdministrationService", "IdentityService", "BasketService", "CatalogService",
"OrderingService", "PaymentService", "CmskitService"
}
);
}
}
@ -264,8 +290,6 @@ 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.");
}

5
shared/EShopOnAbp.Shared.Hosting.Microservices/JwtBearerConfigurationHelper.cs

@ -1,7 +1,6 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;
using System;
using Microsoft.IdentityModel.Tokens;
using Volo.Abp.Modularity;
namespace EShopOnAbp.Shared.Hosting.Microservices;
@ -20,10 +19,6 @@ 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
};
});
}
}

16
tye.yaml

@ -94,14 +94,14 @@ services:
- Kestrel__Certificates__Default__Path=../../../../etc/dev-cert/localhost.pfx
- Kestrel__Certificates__Default__Password=8b6039b6-c67a-448b-977b-0ce6d3fcfd49
- name: public-web
project: apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbp.PublicWeb.csproj
bindings:
- protocol: https
port: 44335
env:
- Kestrel__Certificates__Default__Path=../../../../etc/dev-cert/localhost.pfx
- Kestrel__Certificates__Default__Password=8b6039b6-c67a-448b-977b-0ce6d3fcfd49
# - name: public-web
# project: apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbp.PublicWeb.csproj
# bindings:
# - protocol: https
# port: 44335
# env:
# - Kestrel__Certificates__Default__Path=../../../../etc/dev-cert/localhost.pfx
# - Kestrel__Certificates__Default__Password=8b6039b6-c67a-448b-977b-0ce6d3fcfd49
- name: keycloak-seeder
project: shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj
Loading…
Cancel
Save