diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Microsoft/Extensions/DependencyInjection/AbpJwtBearerExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Microsoft/Extensions/DependencyInjection/AbpJwtBearerExtensions.cs new file mode 100644 index 0000000000..5f48cc92d5 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Microsoft/Extensions/DependencyInjection/AbpJwtBearerExtensions.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Volo.Abp.Security.Claims; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class AbpJwtBearerExtensions +{ + public static AuthenticationBuilder AddAbpJwtBearer(this AuthenticationBuilder builder) + => builder.AddAbpJwtBearer(JwtBearerDefaults.AuthenticationScheme, _ => { }); + + public static AuthenticationBuilder AddAbpJwtBearer(this AuthenticationBuilder builder, Action configureOptions) + => builder.AddAbpJwtBearer(JwtBearerDefaults.AuthenticationScheme, configureOptions); + + public static AuthenticationBuilder AddAbpJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) + => builder.AddAbpJwtBearer(authenticationScheme, "Bearer", configureOptions); + + public static AuthenticationBuilder AddAbpJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) + { + builder.Services.Configure(options => + { + var jwtBearerOption = new JwtBearerOptions(); + configureOptions?.Invoke(jwtBearerOption); + if (!jwtBearerOption.Authority.IsNullOrEmpty()) + { + options.RemoteRefreshUrl = jwtBearerOption.Authority.RemovePostFix("/") + options.RemoteRefreshUrl; + } + }); + + return builder.AddJwtBearer(authenticationScheme, displayName, options => + { + configureOptions?.Invoke(options); + }); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj index a0f4b31158..d43da06dde 100644 --- a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj @@ -18,10 +18,12 @@ + + diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/AbpAspNetCoreAuthenticationJwtBearerModule.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/AbpAspNetCoreAuthenticationJwtBearerModule.cs index 1fa5077a91..3d6bf5e5be 100644 --- a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/AbpAspNetCoreAuthenticationJwtBearerModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/AbpAspNetCoreAuthenticationJwtBearerModule.cs @@ -1,10 +1,24 @@ -using Volo.Abp.Modularity; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims; +using Volo.Abp.Caching; +using Volo.Abp.Modularity; using Volo.Abp.Security; +using Volo.Abp.Security.Claims; namespace Volo.Abp.AspNetCore.Authentication.JwtBearer; -[DependsOn(typeof(AbpSecurityModule))] +[DependsOn(typeof(AbpSecurityModule), typeof(AbpCachingModule))] public class AbpAspNetCoreAuthenticationJwtBearerModule : AbpModule { - + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddHttpClient(); + context.Services.AddHttpContextAccessor(); + var abpClaimsPrincipalFactoryOptions = context.Services.ExecutePreConfiguredActions(); + if (abpClaimsPrincipalFactoryOptions.IsRemoteRefreshEnabled) + { + context.Services.AddTransient(); + context.Services.AddTransient(); + } + } } diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributor.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributor.cs new file mode 100644 index 0000000000..454977b4b6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributor.cs @@ -0,0 +1,10 @@ +using Volo.Abp.DependencyInjection; +using Volo.Abp.Security.Claims; + +namespace Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims; + +[DisableConventionalRegistration] +public class WebRemoteDynamicClaimsPrincipalContributor : RemoteDynamicClaimsPrincipalContributorBase +{ + +} diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributorCache.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributorCache.cs new file mode 100644 index 0000000000..3a3b16131d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributorCache.cs @@ -0,0 +1,75 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using IdentityModel.Client; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.Caching; +using Volo.Abp.Security.Claims; + +namespace Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims; + +public class WebRemoteDynamicClaimsPrincipalContributorCache : RemoteDynamicClaimsPrincipalContributorCacheBase +{ + public const string HttpClientName = nameof(WebRemoteDynamicClaimsPrincipalContributorCache); + + protected IDistributedCache Cache { get; } + protected IHttpClientFactory HttpClientFactory { get; } + protected IHttpContextAccessor HttpContextAccessor { get; } + protected IOptions Options { get; } + + public WebRemoteDynamicClaimsPrincipalContributorCache( + IDistributedCache cache, + IHttpClientFactory httpClientFactory, + IOptions abpClaimsPrincipalFactoryOptions, + IHttpContextAccessor httpContextAccessor, + IOptions options) + : base(abpClaimsPrincipalFactoryOptions) + { + Cache = cache; + HttpClientFactory = httpClientFactory; + HttpContextAccessor = httpContextAccessor; + Options = options; + } + + protected async override Task GetCacheAsync(Guid userId, Guid? tenantId = null) + { + return await Cache.GetAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId)); + } + + protected async override Task RefreshAsync(Guid userId, Guid? tenantId = null) + { + try + { + if (HttpContextAccessor.HttpContext == null) + { + throw new AbpException($"Failed to refresh remote claims for user: {userId} - HttpContext is null!"); + } + + var authenticateResult = await HttpContextAccessor.HttpContext.AuthenticateAsync(Options.Value.AuthenticationScheme); + if (!authenticateResult.Succeeded) + { + throw new AbpException($"Failed to refresh remote claims for user: {userId} - authentication failed!"); + } + + var accessToken = authenticateResult.Properties?.GetTokenValue("access_token"); + if (accessToken.IsNullOrWhiteSpace()) + { + throw new AbpException($"Failed to refresh remote claims for user: {userId} - access_token is null or empty!"); + } + + var client = HttpClientFactory.CreateClient(HttpClientName); + var requestMessage = new HttpRequestMessage(HttpMethod.Post, AbpClaimsPrincipalFactoryOptions.Value.RemoteRefreshUrl); + requestMessage.SetBearerToken(accessToken); + var response = await client.SendAsync(requestMessage); + response.EnsureSuccessStatusCode(); + } + catch (Exception e) + { + Logger.LogWarning(e, $"Failed to refresh remote claims for user: {userId}"); + throw; + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributorOptions.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributorOptions.cs new file mode 100644 index 0000000000..960ebbb38c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributorOptions.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; + +namespace Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims; + +public class WebRemoteDynamicClaimsPrincipalContributorOptions +{ + public string AuthenticationScheme { get; set; } + + public WebRemoteDynamicClaimsPrincipalContributorOptions() + { + AuthenticationScheme = JwtBearerDefaults.AuthenticationScheme; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Microsoft/Extensions/DependencyInjection/AbpOpenIdConnectExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Microsoft/Extensions/DependencyInjection/AbpOpenIdConnectExtensions.cs index 7d2c1fe6d6..d07cca857f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Microsoft/Extensions/DependencyInjection/AbpOpenIdConnectExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Microsoft/Extensions/DependencyInjection/AbpOpenIdConnectExtensions.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Volo.Abp.AspNetCore.Authentication.OpenIdConnect; using Volo.Abp.AspNetCore.MultiTenancy; +using Volo.Abp.Security.Claims; namespace Microsoft.Extensions.DependencyInjection; @@ -24,6 +25,16 @@ public static class AbpOpenIdConnectExtensions public static AuthenticationBuilder AddAbpOpenIdConnect(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) { + builder.Services.Configure(options => + { + var openIdConnectOptions = new OpenIdConnectOptions(); + configureOptions?.Invoke(openIdConnectOptions); + if (!openIdConnectOptions.Authority.IsNullOrEmpty()) + { + options.RemoteRefreshUrl = openIdConnectOptions.Authority.RemovePostFix("/") + options.RemoteRefreshUrl; + } + }); + return builder.AddOpenIdConnect(authenticationScheme, displayName, options => { options.ClaimActions.MapAbpClaimTypes(); @@ -38,7 +49,7 @@ public static class AbpOpenIdConnectExtensions }; options.AccessDeniedPath = "/"; - + options.Events.OnTokenValidated = async (context) => { var client = context.HttpContext.RequestServices.GetRequiredService(); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientCommonModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientCommonModule.cs index d3734e7c66..b7cd9d3a30 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientCommonModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientCommonModule.cs @@ -7,6 +7,7 @@ using Volo.Abp.Features; using Volo.Abp.Http.Client; using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.Security.Claims; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.AspNetCore.Mvc.Client; @@ -40,5 +41,12 @@ public class AbpAspNetCoreMvcClientCommonModule : AbpModule context.Services.AddTransient(); context.Services.AddTransient(); + + var abpClaimsPrincipalFactoryOptions = context.Services.ExecutePreConfiguredActions(); + if (abpClaimsPrincipalFactoryOptions.IsRemoteRefreshEnabled) + { + context.Services.AddTransient(); + context.Services.AddTransient(); + } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributor.cs index d9339b57df..ae88d00ef8 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributor.cs @@ -1,44 +1,10 @@ -using System; -using System.Linq; -using System.Security.Claims; -using System.Security.Principal; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; +using Volo.Abp.DependencyInjection; using Volo.Abp.Security.Claims; namespace Volo.Abp.AspNetCore.Mvc.Client; -public class RemoteDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincipalContributorBase +[DisableConventionalRegistration] +public class RemoteDynamicClaimsPrincipalContributor : RemoteDynamicClaimsPrincipalContributorBase { - public async override Task ContributeAsync(AbpClaimsPrincipalContributorContext context) - { - var identity = context.ClaimsPrincipal.Identities.FirstOrDefault(); - if (identity == null) - { - return; - } - var userId = identity.FindUserId(); - if (userId == null) - { - return; - } - - var dynamicClaimsCache = context.GetRequiredService(); - AbpDynamicClaimCacheItem dynamicClaims; - try - { - dynamicClaims = await dynamicClaimsCache.GetAsync(userId.Value, identity.FindTenantId()); - } - catch (Exception e) - { - // In case if failed refresh remote dynamic cache, We force to clear the claims principal. - context.ClaimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity()); - var logger = context.GetRequiredService>(); - logger.LogWarning(e, $"Failed to refresh remote dynamic claims cache for user: {userId.Value}"); - return; - } - - await AddDynamicClaimsAsync(context, identity, dynamicClaims.Claims); - } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs index e9b03e847c..738884fe06 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs @@ -3,24 +3,20 @@ using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Volo.Abp.Caching; -using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client; using Volo.Abp.Http.Client.Authentication; using Volo.Abp.Security.Claims; namespace Volo.Abp.AspNetCore.Mvc.Client; -public class RemoteDynamicClaimsPrincipalContributorCache : ITransientDependency +public class RemoteDynamicClaimsPrincipalContributorCache : RemoteDynamicClaimsPrincipalContributorCacheBase { public const string HttpClientName = nameof(RemoteDynamicClaimsPrincipalContributorCache); - public ILogger Logger { get; set; } protected IDistributedCache Cache { get; } protected IHttpClientFactory HttpClientFactory { get; } - protected IOptions AbpClaimsPrincipalFactoryOptions { get; } protected IRemoteServiceHttpClientAuthenticator HttpClientAuthenticator { get; } public RemoteDynamicClaimsPrincipalContributorCache( @@ -28,25 +24,20 @@ public class RemoteDynamicClaimsPrincipalContributorCache : ITransientDependency IHttpClientFactory httpClientFactory, IOptions abpClaimsPrincipalFactoryOptions, IRemoteServiceHttpClientAuthenticator httpClientAuthenticator) + : base(abpClaimsPrincipalFactoryOptions) { Cache = cache; HttpClientFactory = httpClientFactory; - AbpClaimsPrincipalFactoryOptions = abpClaimsPrincipalFactoryOptions; HttpClientAuthenticator = httpClientAuthenticator; - - Logger = NullLogger.Instance; } - public virtual async Task GetAsync(Guid userId, Guid? tenantId = null) + protected async override Task GetCacheAsync(Guid userId, Guid? tenantId = null) { - Logger.LogDebug($"Get dynamic claims cache for user: {userId}"); - var dynamicClaims = await Cache.GetAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId)); - if (dynamicClaims != null && !dynamicClaims.Claims.IsNullOrEmpty()) - { - return dynamicClaims; - } + return await Cache.GetAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId)); + } - Logger.LogDebug($"Refresh dynamic claims for user: {userId} from remote service."); + protected async override Task RefreshAsync(Guid userId, Guid? tenantId = null) + { try { var client = HttpClientFactory.CreateClient(HttpClientName); @@ -60,13 +51,5 @@ public class RemoteDynamicClaimsPrincipalContributorCache : ITransientDependency Logger.LogWarning(e, $"Failed to refresh remote claims for user: {userId}"); throw; } - - dynamicClaims = await Cache.GetAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId)); - if (dynamicClaims == null || dynamicClaims.Claims.IsNullOrEmpty()) - { - throw new AbpException($"Failed to refresh remote claims for user: {userId}"); - } - - return dynamicClaims!; } } diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs index 75bf67bee6..1ed46c0def 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs @@ -12,11 +12,13 @@ public class AbpClaimsPrincipalFactoryOptions public List DynamicClaims { get; } + public bool IsRemoteRefreshEnabled { get; set; } + public string RemoteRefreshUrl { get; set; } public Dictionary> ClaimsMap { get; set; } - public bool IsDynamicClaimsEnabled { get; set; } + public bool IsDynamicClaimsEnabled { get; set; } public AbpClaimsPrincipalFactoryOptions() { @@ -36,6 +38,7 @@ public class AbpClaimsPrincipalFactoryOptions }; RemoteRefreshUrl = "/api/account/dynamic-claims/refresh"; + IsRemoteRefreshEnabled = true; ClaimsMap = new Dictionary>() { diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/RemoteDynamicClaimsPrincipalContributorBase.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/RemoteDynamicClaimsPrincipalContributorBase.cs new file mode 100644 index 0000000000..b54f5becbd --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/RemoteDynamicClaimsPrincipalContributorBase.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace Volo.Abp.Security.Claims; + +public abstract class RemoteDynamicClaimsPrincipalContributorBase : AbpDynamicClaimsPrincipalContributorBase + where TContributor : class + where TContributorCache : RemoteDynamicClaimsPrincipalContributorCacheBase +{ + public async override Task ContributeAsync(AbpClaimsPrincipalContributorContext context) + { + var identity = context.ClaimsPrincipal.Identities.FirstOrDefault(); + if (identity == null) + { + return; + } + + var userId = identity.FindUserId(); + if (userId == null) + { + return; + } + + var dynamicClaimsCache = context.GetRequiredService().As(); + AbpDynamicClaimCacheItem dynamicClaims; + try + { + dynamicClaims = await dynamicClaimsCache.GetAsync(userId.Value, identity.FindTenantId()); + } + catch (Exception e) + { + // In case if failed refresh remote dynamic cache, We force to clear the claims principal. + context.ClaimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity()); + var logger = context.GetRequiredService>(); + logger.LogWarning(e, $"Failed to refresh remote dynamic claims cache for user: {userId.Value}"); + return; + } + + if (dynamicClaims.Claims.IsNullOrEmpty()) + { + return; + } + + await AddDynamicClaimsAsync(context, identity, dynamicClaims.Claims); + } +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/RemoteDynamicClaimsPrincipalContributorCacheBase.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/RemoteDynamicClaimsPrincipalContributorCacheBase.cs new file mode 100644 index 0000000000..fce2324812 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/RemoteDynamicClaimsPrincipalContributorCacheBase.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; + +namespace Volo.Abp.Security.Claims; + +public abstract class RemoteDynamicClaimsPrincipalContributorCacheBase +{ + public ILogger Logger { get; set; } + + protected IOptions AbpClaimsPrincipalFactoryOptions { get; } + + protected RemoteDynamicClaimsPrincipalContributorCacheBase(IOptions abpClaimsPrincipalFactoryOptions) + { + AbpClaimsPrincipalFactoryOptions = abpClaimsPrincipalFactoryOptions; + + Logger = NullLogger.Instance; + } + + public async Task GetAsync(Guid userId, Guid? tenantId = null) + { + Logger.LogDebug($"Get dynamic claims cache for user: {userId}"); + var dynamicClaims = await GetCacheAsync(userId, tenantId); + if (dynamicClaims != null) + { + return dynamicClaims; + } + + Logger.LogDebug($"Refresh dynamic claims for user: {userId} from remote service."); + try + { + await RefreshAsync(userId, tenantId); + } + catch (Exception e) + { + Logger.LogWarning(e, $"Failed to refresh remote claims for user: {userId}"); + throw; + } + + dynamicClaims = await GetCacheAsync(userId, tenantId); + if (dynamicClaims == null) + { + throw new AbpException($"Failed to refresh remote claims for user: {userId}"); + } + + return dynamicClaims; + } + + protected abstract Task GetCacheAsync(Guid userId, Guid? tenantId = null); + + protected abstract Task RefreshAsync(Guid userId, Guid? tenantId = null); +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs index 354e7e4586..49f353153b 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs @@ -25,6 +25,14 @@ public class AbpIdentityDomainModule : AbpModule { private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(options => + { + options.IsRemoteRefreshEnabled = false; + }); + } + public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddAutoMapperObjectMapper(); diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributor.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributor.cs index 7d56a3b184..94a55b205e 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributor.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributor.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Security.Principal; @@ -34,6 +35,11 @@ public class IdentityDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincip return; } + if (dynamicClaims.Claims.IsNullOrEmpty()) + { + return; + } + await AddDynamicClaimsAsync(context, identity, dynamicClaims.Claims); } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributorCache.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributorCache.cs index b0ab885e03..74d395e692 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributorCache.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributorCache.cs @@ -47,6 +47,17 @@ public class IdentityDynamicClaimsPrincipalContributorCache : ITransientDependen { Logger.LogDebug($"Get dynamic claims cache for user: {userId}"); + if (AbpClaimsPrincipalFactoryOptions.Value.DynamicClaims.IsNullOrEmpty()) + { + var emptyCacheItem = new AbpDynamicClaimCacheItem(); + await Cache.SetAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId), emptyCacheItem, new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = CacheOptions.Value.CacheAbsoluteExpiration + }); + + return emptyCacheItem; + } + return await Cache.GetOrAddAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId), async () => { using (CurrentTenant.Change(tenantId)) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs index c41ed749be..e456ede4f3 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs @@ -232,7 +232,6 @@ public class MyProjectNameBlazorModule : AbpModule context.Services.Configure(options => { options.IsDynamicClaimsEnabled = true; - options.RemoteRefreshUrl = configuration["AuthServer:Authority"] + options.RemoteRefreshUrl; }); } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs index 70e98a64eb..777d3e8886 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs @@ -214,7 +214,6 @@ public class MyProjectNameWebModule : AbpModule context.Services.Configure(options => { options.IsDynamicClaimsEnabled = true; - options.RemoteRefreshUrl = configuration["AuthServer:Authority"] + options.RemoteRefreshUrl; }); }