From 9aae95d2436345c19af5332f145deb81c4f3de6e Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 17 Nov 2023 14:57:28 +0800 Subject: [PATCH] Add `Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims`. --- framework/Volo.Abp.sln | 7 ++ ...hentication.JwtBearer.DynamicClaims.csproj | 27 +++++++ ...henticationJwtBearerDynamicClaimsModule.cs | 25 +++++++ ...RemoteDynamicClaimsPrincipalContributor.cs | 10 +++ ...eDynamicClaimsPrincipalContributorCache.cs | 75 +++++++++++++++++++ ...ynamicClaimsPrincipalContributorOptions.cs | 13 ++++ .../AbpAspNetCoreMvcClientCommonModule.cs | 8 ++ ...RemoteDynamicClaimsPrincipalContributor.cs | 46 +----------- ...eDynamicClaimsPrincipalContributorCache.cs | 31 ++------ .../AbpClaimsPrincipalFactoryOptions.cs | 5 +- ...teDynamicClaimsPrincipalContributorBase.cs | 51 +++++++++++++ ...amicClaimsPrincipalContributorCacheBase.cs | 55 ++++++++++++++ .../Abp/Identity/AbpIdentityDomainModule.cs | 8 ++ nupkg/common.ps1 | 1 + .../MyProjectNameBlazorModule.cs | 1 - .../MyProjectNameWebModule.cs | 1 - 16 files changed, 294 insertions(+), 70 deletions(-) create mode 100644 framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims.csproj create mode 100644 framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/AbpAspNetCoreAuthenticationJwtBearerDynamicClaimsModule.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributor.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributorCache.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributorOptions.cs create mode 100644 framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/RemoteDynamicClaimsPrincipalContributorBase.cs create mode 100644 framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/RemoteDynamicClaimsPrincipalContributorCacheBase.cs diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 42339a60ac..c06ede33c1 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -463,6 +463,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.SkiaSharp" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.SkiaSharp.Tests", "test\Volo.Abp.Imaging.SkiaSharp.Tests\Volo.Abp.Imaging.SkiaSharp.Tests.csproj", "{DFAF8763-D1D6-4EB4-B459-20E31007FE2F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims", "src\Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims\Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims.csproj", "{6F72FFCE-0183-4EF1-80B6-8FC3268E8E99}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1381,6 +1383,10 @@ Global {DFAF8763-D1D6-4EB4-B459-20E31007FE2F}.Debug|Any CPU.Build.0 = Debug|Any CPU {DFAF8763-D1D6-4EB4-B459-20E31007FE2F}.Release|Any CPU.ActiveCfg = Release|Any CPU {DFAF8763-D1D6-4EB4-B459-20E31007FE2F}.Release|Any CPU.Build.0 = Release|Any CPU + {6F72FFCE-0183-4EF1-80B6-8FC3268E8E99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6F72FFCE-0183-4EF1-80B6-8FC3268E8E99}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F72FFCE-0183-4EF1-80B6-8FC3268E8E99}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6F72FFCE-0183-4EF1-80B6-8FC3268E8E99}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1614,6 +1620,7 @@ Global {F19A6E0C-F719-4ED9-A024-14E4B8D40883} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {198683D0-7DC6-40F2-B81B-8E446E70A9DE} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {DFAF8763-D1D6-4EB4-B459-20E31007FE2F} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {6F72FFCE-0183-4EF1-80B6-8FC3268E8E99} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims.csproj b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims.csproj new file mode 100644 index 0000000000..d02a8df68c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims.csproj @@ -0,0 +1,27 @@ + + + + + + + net8.0 + enable + Nullable + Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/AbpAspNetCoreAuthenticationJwtBearerDynamicClaimsModule.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/AbpAspNetCoreAuthenticationJwtBearerDynamicClaimsModule.cs new file mode 100644 index 0000000000..a21bf70d6d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/AbpAspNetCoreAuthenticationJwtBearerDynamicClaimsModule.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Caching; +using Volo.Abp.Modularity; +using Volo.Abp.Security.Claims; + +namespace Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims; + +[DependsOn( + typeof(AbpAspNetCoreAuthenticationJwtBearerModule), + typeof(AbpCachingModule) +)] +public class AbpAspNetCoreAuthenticationJwtBearerDynamicClaimsModule : 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.DynamicClaims/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributor.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims/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.DynamicClaims/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.DynamicClaims/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributorCache.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims/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.DynamicClaims/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.DynamicClaims/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributorOptions.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims/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.DynamicClaims/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.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 2ab437b319..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,50 +1,10 @@ -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; +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; - } - - if (dynamicClaims.Claims.IsNullOrEmpty()) - { - 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 9dac39c786..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) - { - 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..b738aa54d5 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"; + IsDynamicClaimsEnabled = 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..7c7c392157 --- /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..cc576da441 --- /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 && !dynamicClaims.Claims.IsNullOrEmpty()) + { + 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/nupkg/common.ps1 b/nupkg/common.ps1 index 78de2aef44..db18b918b3 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -95,6 +95,7 @@ $projects = ( # framework "framework/src/Volo.Abp.ApiVersioning.Abstractions", "framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer", + "framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims", "framework/src/Volo.Abp.AspNetCore.Authentication.OAuth", "framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect", "framework/src/Volo.Abp.AspNetCore", 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; }); }