Add `RemoteDynamicClaimsPrincipalContributor`.

pull/18064/head
maliming 2 years ago
parent 9a1b4c2004
commit 02bd699c4d

@ -1,9 +1,9 @@
using System.Security.Claims;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Security.Claims;
namespace Volo.Abp.AspNetCore.Components.Web.Security;
@ -12,16 +12,18 @@ public class AbpComponentsClaimsCache : IScopedDependency
public ClaimsPrincipal Principal { get; private set; } = default!;
private readonly AuthenticationStateProvider? _authenticationStateProvider;
private readonly IAbpClaimsPrincipalFactory _abpClaimsPrincipalFactory;
public AbpComponentsClaimsCache(
IClientScopeServiceProviderAccessor serviceProviderAccessor)
{
_authenticationStateProvider = serviceProviderAccessor.ServiceProvider.GetService<AuthenticationStateProvider>();
_abpClaimsPrincipalFactory = serviceProviderAccessor.ServiceProvider.GetRequiredService<IAbpClaimsPrincipalFactory>();
if (_authenticationStateProvider != null)
{
_authenticationStateProvider.AuthenticationStateChanged += async (task) =>
{
Principal = (await task).User;
Principal = await _abpClaimsPrincipalFactory.CreateDynamicAsync((await task).User);
};
}
}
@ -32,6 +34,7 @@ public class AbpComponentsClaimsCache : IScopedDependency
{
var authenticationState = await _authenticationStateProvider.GetAuthenticationStateAsync();
Principal = authenticationState.User;
await _abpClaimsPrincipalFactory.CreateDynamicAsync(Principal);
}
}
}

@ -0,0 +1,30 @@
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Volo.Abp.Security.Claims;
namespace Volo.Abp.AspNetCore.Mvc.Client;
public class RemoteDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincipalContributorBase
{
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<RemoteDynamicClaimsPrincipalContributorCache>();
var dynamicClaims = await dynamicClaimsCache.GetAsync(userId.Value, identity.FindTenantId());
await MapCommonClaimsAsync(identity, dynamicClaims);
await AddDynamicClaims(identity, dynamicClaims);
}
}

@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
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.Json;
using Volo.Abp.Security.Claims;
namespace Volo.Abp.AspNetCore.Mvc.Client;
public class RemoteDynamicClaimsPrincipalContributorCache : ITransientDependency
{
public const string HttpClientName = nameof(RemoteDynamicClaimsPrincipalContributorCache);
public ILogger<RemoteDynamicClaimsPrincipalContributorCache> Logger { get; set; }
protected IDistributedCache<List<AbpClaimCacheItem>> Cache { get; }
protected IHttpClientFactory HttpClientFactory { get; }
protected IOptions<AbpClaimsPrincipalFactoryOptions> AbpClaimsPrincipalFactoryOptions { get; }
protected IJsonSerializer JsonSerializer { get; }
protected IRemoteServiceHttpClientAuthenticator HttpClientAuthenticator { get; }
protected IOptions<RemoteDynamicClaimsPrincipalContributorCacheOptions> CacheOptions { get; }
public RemoteDynamicClaimsPrincipalContributorCache(
IDistributedCache<List<AbpClaimCacheItem>> cache,
IHttpClientFactory httpClientFactory,
IOptions<AbpClaimsPrincipalFactoryOptions> abpClaimsPrincipalFactoryOptions,
IJsonSerializer jsonSerializer,
IRemoteServiceHttpClientAuthenticator httpClientAuthenticator,
IOptions<RemoteDynamicClaimsPrincipalContributorCacheOptions> cacheOptions)
{
Cache = cache;
HttpClientFactory = httpClientFactory;
AbpClaimsPrincipalFactoryOptions = abpClaimsPrincipalFactoryOptions;
JsonSerializer = jsonSerializer;
HttpClientAuthenticator = httpClientAuthenticator;
CacheOptions = cacheOptions;
Logger = NullLogger<RemoteDynamicClaimsPrincipalContributorCache>.Instance;
}
public virtual async Task<List<AbpClaimCacheItem>> GetAsync(Guid userId, Guid? tenantId = null)
{
Logger.LogDebug($"Get dynamic claims cache for user: {userId}");
//The UI may use the same cache as AuthServer in the tiered application.
var claims = await Cache.GetAsync(AbpClaimCacheItem.CalculateCacheKey(userId, tenantId));
if (!claims.IsNullOrEmpty())
{
return claims!;
}
Logger.LogDebug($"Get dynamic claims cache for user: {userId} from remote cache.");
// Use independent cache for remote claims.
return (await Cache.GetOrAddAsync($"{nameof(RemoteDynamicClaimsPrincipalContributorCache)}{AbpClaimCacheItem.CalculateCacheKey(userId, tenantId)}", async () =>
{
var dynamicClaims = new List<AbpClaimCacheItem>();
Logger.LogDebug($"Get dynamic claims for user: {userId} from remote service.");
try
{
var client = HttpClientFactory.CreateClient(HttpClientName);
var requestMessage = new HttpRequestMessage(HttpMethod.Get, AbpClaimsPrincipalFactoryOptions.Value.RemoteUrl);
await HttpClientAuthenticator.Authenticate(new RemoteServiceHttpClientAuthenticateContext(client, requestMessage, new RemoteServiceConfiguration("/"), string.Empty));
var response = await client.SendAsync(requestMessage);
dynamicClaims = JsonSerializer.Deserialize<List<AbpClaimCacheItem>>(await response.Content.ReadAsStringAsync());
Logger.LogDebug($"Successfully got {dynamicClaims.Count} remote claims for user: {userId}");
}
catch (Exception e)
{
Logger.LogWarning(e, $"Failed to get remote claims for user: {userId}");
}
return dynamicClaims;
}, () => new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = CacheOptions.Value.CacheAbsoluteExpiration
}))!;
}
public virtual async Task ClearAsync(Guid userId, Guid? tenantId = null)
{
Logger.LogDebug($"Clear dynamic claims cache for user: {userId}");
Logger.LogDebug($"Clear dynamic claims cache from remote cache for user: {userId}");
await Cache.RemoveAsync(AbpClaimCacheItem.CalculateCacheKey(userId, tenantId));
await Cache.RemoveAsync($"{nameof(RemoteDynamicClaimsPrincipalContributorCache)}{AbpClaimCacheItem.CalculateCacheKey(userId, tenantId)}");
}
}

@ -0,0 +1,13 @@
using System;
namespace Volo.Abp.AspNetCore.Mvc.Client;
public class RemoteDynamicClaimsPrincipalContributorCacheOptions
{
public TimeSpan CacheAbsoluteExpiration { get; set; }
public RemoteDynamicClaimsPrincipalContributorCacheOptions()
{
CacheAbsoluteExpiration = TimeSpan.FromSeconds(5);
}
}

@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
using Volo.Abp.Security.Claims;
using Volo.Abp.Security.Encryption;
using Volo.Abp.SecurityLog;
@ -9,6 +11,11 @@ namespace Volo.Abp.Security;
public class AbpSecurityModule : AbpModule
{
public override void PostConfigureServices(ServiceConfigurationContext context)
{
AutoAddClaimsPrincipalContributors(context.Services);
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
var applicationName = context.Services.GetApplicationName();
@ -51,4 +58,35 @@ public class AbpSecurityModule : AbpModule
}
});
}
private static void AutoAddClaimsPrincipalContributors(IServiceCollection services)
{
var contributorTypes = new List<Type>();
services.OnRegistered(context =>
{
if (typeof(IAbpClaimsPrincipalContributor).IsAssignableFrom(context.ImplementationType) &&
!typeof(IAbpDynamicClaimsPrincipalContributor).IsAssignableFrom(context.ImplementationType))
{
contributorTypes.Add(context.ImplementationType);
}
});
var dynamicContributorTypes = new List<Type>();
services.OnRegistered(context =>
{
if (typeof(IAbpDynamicClaimsPrincipalContributor).IsAssignableFrom(context.ImplementationType))
{
dynamicContributorTypes.Add(context.ImplementationType);
}
});
services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
{
options.Contributors.AddIfNotContains(contributorTypes);
options.DynamicContributors.AddIfNotContains(dynamicContributorTypes);
});
}
}

@ -14,4 +14,11 @@ public class AbpClaimCacheItem
Type = type;
Value = value;
}
public static string CalculateCacheKey(Guid userId, Guid? tenantId)
{
return $"{tenantId}-{userId}";
}
}

@ -11,7 +11,7 @@ public class AbpClaimsPrincipalFactoryOptions
public List<string> DynamicClaims { get; }
public string RemoteRefreshUrl { get; set; }
public string RemoteUrl { get; set; }
public AbpClaimsPrincipalFactoryOptions()
{
@ -21,12 +21,15 @@ public class AbpClaimsPrincipalFactoryOptions
DynamicClaims = new List<string>
{
AbpClaimTypes.UserName,
AbpClaimTypes.Name,
AbpClaimTypes.SurName,
AbpClaimTypes.Role,
AbpClaimTypes.Email,
AbpClaimTypes.EmailVerified,
AbpClaimTypes.PhoneNumber,
AbpClaimTypes.PhoneNumberVerified
};
RemoteRefreshUrl = "/api/account/refresh-dynamic-claims";
RemoteUrl = "/api/account/dynamic-claims";
}
}

@ -0,0 +1,74 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Security.Claims;
public abstract class AbpDynamicClaimsPrincipalContributorBase : IAbpDynamicClaimsPrincipalContributor, ITransientDependency
{
public abstract Task ContributeAsync(AbpClaimsPrincipalContributorContext context);
protected virtual async Task MapCommonClaimsAsync(ClaimsIdentity identity, List<AbpClaimCacheItem> dynamicClaims)
{
await MapClaimsAsync(identity, dynamicClaims, identity.NameClaimType, "preferred_username");
await MapClaimsAsync(identity, dynamicClaims, identity.NameClaimType, ClaimTypes.Name);
await MapClaimsAsync(identity, dynamicClaims, identity.RoleClaimType, "role");
await MapClaimsAsync(identity, dynamicClaims, identity.RoleClaimType, ClaimTypes.Role);
await MapClaimsAsync(identity, dynamicClaims, "email", ClaimTypes.Email);
await MapClaimsAsync(identity, dynamicClaims, "family_name", ClaimTypes.Surname);
await MapClaimsAsync(identity, dynamicClaims, "given_name", ClaimTypes.GivenName);
}
protected virtual Task MapClaimsAsync(ClaimsIdentity identity, List<AbpClaimCacheItem> dynamicClaims, string sourceType, string targetType)
{
if (sourceType == targetType)
{
return Task.CompletedTask;;
}
if (identity.Claims.Any(c => c.Type == sourceType) && dynamicClaims.All(c => c.Type != sourceType))
{
var claims = dynamicClaims.Where(c => c.Type == targetType).ToList();
if (!claims.IsNullOrEmpty())
{
identity.RemoveAll(sourceType);
identity.AddClaims(claims.Select(c => new Claim(sourceType, c.Value)));
dynamicClaims.RemoveAll(c => c.Type == targetType);
}
}
if (identity.Claims.Any(c => c.Type == targetType) && dynamicClaims.All(c => c.Type != targetType))
{
var claims = dynamicClaims.Where(c => c.Type == sourceType).ToList();
if (!claims.IsNullOrEmpty())
{
identity.RemoveAll(targetType);
identity.AddClaims(claims.Select(c => new Claim(targetType, c.Value)));
dynamicClaims.RemoveAll(c => c.Type == sourceType);
}
}
return Task.CompletedTask;;
}
protected virtual Task AddDynamicClaims(ClaimsIdentity identity, List<AbpClaimCacheItem> dynamicClaims)
{
foreach (var claims in dynamicClaims.GroupBy(x => x.Type))
{
if (claims.Count() > 1)
{
identity.RemoveAll(claims.First().Type);
identity.AddClaims(claims.Select(c => new Claim(claims.First().Type, c.Value)));
}
else
{
identity.AddOrReplace(new Claim(claims.First().Type, claims.First().Value));
}
}
return Task.CompletedTask;;
}
}

@ -0,0 +1,6 @@
namespace Volo.Abp.Security.Claims;
public interface IAbpDynamicClaimsPrincipalContributor : IAbpClaimsPrincipalContributor
{
}

@ -2,8 +2,8 @@
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Testing;
using Xunit;
@ -25,26 +25,6 @@ public class AbpClaimsPrincipalFactory_Test : AbpIntegratedTest<AbpSecurityTestM
options.UseAutofac();
}
protected override void AfterAddApplication(IServiceCollection services)
{
services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
{
options.Contributors.Add<TestAbpClaimsPrincipalContributor>();
options.Contributors.Add<Test2AbpClaimsPrincipalContributor>();
options.Contributors.Add<Test3AbpClaimsPrincipalContributor>();
options.DynamicContributors.Add<TestAbpClaimsPrincipalContributor>();
options.DynamicContributors.Add<Test2AbpClaimsPrincipalContributor>();
options.DynamicContributors.Add<Test3AbpClaimsPrincipalContributor>();
options.DynamicContributors.Add<Test4AbpClaimsPrincipalContributor>();
});
services.AddTransient<TestAbpClaimsPrincipalContributor>();
services.AddTransient<Test2AbpClaimsPrincipalContributor>();
services.AddTransient<Test3AbpClaimsPrincipalContributor>();
services.AddTransient<Test4AbpClaimsPrincipalContributor>();
}
[Fact]
public async Task CreateAsync()
{
@ -99,7 +79,52 @@ public class AbpClaimsPrincipalFactory_Test : AbpIntegratedTest<AbpSecurityTestM
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Version && x.Value == "2.0");
}
class TestAbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor
class TestAbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency
{
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context)
{
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType)
?? new ClaimsIdentity(TestAuthenticationType);
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Email, "admin@abp.io"));
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity);
return Task.CompletedTask;
}
}
class Test2AbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency
{
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context)
{
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType)
?? new ClaimsIdentity(TestAuthenticationType);
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Email, "admin2@abp.io"));
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity);
return Task.CompletedTask;
}
}
class Test3AbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency
{
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context)
{
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType)
?? new ClaimsIdentity(TestAuthenticationType);
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Version, "2.0"));
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity);
return Task.CompletedTask;
}
}
class TestAbpDynamicClaimsPrincipalContributor : IAbpDynamicClaimsPrincipalContributor, ITransientDependency
{
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context)
{
@ -114,7 +139,7 @@ public class AbpClaimsPrincipalFactory_Test : AbpIntegratedTest<AbpSecurityTestM
}
}
class Test2AbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor
class Test2AbpDynamicClaimsPrincipalContributor : IAbpDynamicClaimsPrincipalContributor, ITransientDependency
{
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context)
{
@ -129,7 +154,7 @@ public class AbpClaimsPrincipalFactory_Test : AbpIntegratedTest<AbpSecurityTestM
}
}
class Test3AbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor
class Test3AbpDynamicClaimsPrincipalContributor : IAbpDynamicClaimsPrincipalContributor, ITransientDependency
{
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context)
{
@ -144,7 +169,7 @@ public class AbpClaimsPrincipalFactory_Test : AbpIntegratedTest<AbpSecurityTestM
}
}
class Test4AbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor
class Test4AbpDynamicClaimsPrincipalContributor : IAbpDynamicClaimsPrincipalContributor, ITransientDependency
{
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context)
{

@ -0,0 +1,8 @@
namespace Volo.Abp.Account;
public class DynamicClaimDto
{
public string Type { get; set; }
public string Value { get; set; }
}

@ -0,0 +1,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace Volo.Abp.Account;
public interface IDynamicClaimsAppService: IApplicationService
{
Task<List<DynamicClaimDto>> GetAsync();
}

@ -0,0 +1,42 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using Volo.Abp.Identity;
using Volo.Abp.Security.Claims;
namespace Volo.Abp.Account;
[Authorize]
public class DynamicClaimsAppService : IdentityAppServiceBase, IDynamicClaimsAppService
{
protected IAbpClaimsPrincipalFactory AbpClaimsPrincipalFactory { get; }
protected ICurrentPrincipalAccessor PrincipalAccessor { get; }
protected IOptions<AbpClaimsPrincipalFactoryOptions> AbpClaimsPrincipalFactoryOptions { get; }
public DynamicClaimsAppService(
IAbpClaimsPrincipalFactory abpClaimsPrincipalFactory,
ICurrentPrincipalAccessor principalAccessor,
IOptions<AbpClaimsPrincipalFactoryOptions> abpClaimsPrincipalFactoryOptions)
{
AbpClaimsPrincipalFactory = abpClaimsPrincipalFactory;
PrincipalAccessor = principalAccessor;
AbpClaimsPrincipalFactoryOptions = abpClaimsPrincipalFactoryOptions;
}
public virtual async Task<List<DynamicClaimDto>> GetAsync()
{
var principal = await AbpClaimsPrincipalFactory.CreateAsync(PrincipalAccessor.Principal);
var dynamicClaims = principal.Claims
.Where(c => AbpClaimsPrincipalFactoryOptions.Value.DynamicClaims.Contains(c.Type))
.Select(c => new DynamicClaimDto
{
Type = c.Type,
Value = c.Value
});
return dynamicClaims.ToList();
}
}

@ -0,0 +1,24 @@
// This file is automatically generated by ABP framework to use MVC Controllers from CSharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Account;
using Volo.Abp.Application.Dtos;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client;
using Volo.Abp.Http.Client.ClientProxying;
using Volo.Abp.Http.Modeling;
// ReSharper disable once CheckNamespace
namespace Volo.Abp.Account;
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IDynamicClaimsAppService), typeof(DynamicClaimsClientProxy))]
public partial class DynamicClaimsClientProxy : ClientProxyBase<IDynamicClaimsAppService>, IDynamicClaimsAppService
{
public virtual async Task<List<DynamicClaimDto>> GetAsync()
{
return await RequestAsync<List<DynamicClaimDto>>(nameof(GetAsync));
}
}

@ -0,0 +1,7 @@
// This file is part of DynamicClaimsClientProxy, you can customize it here
// ReSharper disable once CheckNamespace
namespace Volo.Abp.Account;
public partial class DynamicClaimsClientProxy
{
}

@ -338,6 +338,47 @@
}
}
},
"Volo.Abp.Account.DynamicClaimsController": {
"controllerName": "DynamicClaims",
"controllerGroupName": "DynamicClaims",
"isRemoteService": true,
"isIntegrationService": false,
"apiVersion": null,
"type": "Volo.Abp.Account.DynamicClaimsController",
"interfaces": [
{
"type": "Volo.Abp.Account.IDynamicClaimsAppService",
"name": "IDynamicClaimsAppService",
"methods": [
{
"name": "GetAsync",
"parametersOnMethod": [],
"returnValue": {
"type": "System.Collections.Generic.List<Volo.Abp.Account.DynamicClaimDto>",
"typeSimple": "[Volo.Abp.Account.DynamicClaimDto]"
}
}
]
}
],
"actions": {
"GetAsync": {
"uniqueName": "GetAsync",
"name": "GetAsync",
"httpMethod": "GET",
"url": "api/account/dynamic-claims",
"supportedVersions": [],
"parametersOnMethod": [],
"parameters": [],
"returnValue": {
"type": "System.Collections.Generic.List<Volo.Abp.Account.DynamicClaimDto>",
"typeSimple": "[Volo.Abp.Account.DynamicClaimDto]"
},
"allowAnonymous": null,
"implementFrom": "Volo.Abp.Account.IDynamicClaimsAppService"
}
}
},
"Volo.Abp.Account.ProfileController": {
"controllerName": "Profile",
"controllerGroupName": "Profile",

@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
namespace Volo.Abp.Account;
[RemoteService(Name = AccountRemoteServiceConsts.RemoteServiceName)]
[Area(AccountRemoteServiceConsts.ModuleName)]
[ControllerName("DynamicClaims")]
[Route("/api/account/dynamic-claims")]
public class DynamicClaimsController : AbpControllerBase, IDynamicClaimsAppService
{
protected IDynamicClaimsAppService DynamicClaimsAppService { get; }
public DynamicClaimsController(IDynamicClaimsAppService dynamicClaimsAppService)
{
DynamicClaimsAppService = dynamicClaimsAppService;
}
[HttpGet]
public virtual Task<List<DynamicClaimDto>> GetAsync()
{
return DynamicClaimsAppService.GetAsync();
}
}

@ -79,6 +79,21 @@
})();
// controller volo.abp.account.dynamicClaims
(function(){
abp.utils.createNamespace(window, 'volo.abp.account.dynamicClaims');
volo.abp.account.dynamicClaims.get = function(ajaxParams) {
return abp.ajax($.extend(true, {
url: abp.appPath + 'api/account/dynamic-claims',
type: 'GET'
}, ajaxParams));
};
})();
// controller volo.abp.account.profile
(function(){

@ -62,11 +62,6 @@ public class AbpIdentityDomainModule : AbpModule
});
context.Services.AddAbpDynamicOptions<IdentityOptions, AbpIdentityOptionsManager>();
Configure<AbpClaimsPrincipalFactoryOptions>(options =>
{
options.DynamicContributors.Add<IdentityDynamicClaimsPrincipalContributor>();
});
}
public override void PostConfigureServices(ServiceConfigurationContext context)

@ -1,15 +1,13 @@
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Security.Claims;
namespace Volo.Abp.Identity;
public class IdentityDynamicClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency
public class IdentityDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincipalContributorBase
{
public virtual async Task ContributeAsync(AbpClaimsPrincipalContributorContext context)
public async override Task ContributeAsync(AbpClaimsPrincipalContributorContext context)
{
var identity = context.ClaimsPrincipal.Identities.FirstOrDefault();
var userId = identity?.FindUserId();
@ -18,11 +16,10 @@ public class IdentityDynamicClaimsPrincipalContributor : IAbpClaimsPrincipalCont
return;
}
var cache = context.GetRequiredService<IdentityDynamicClaimsPrincipalContributorCache>();
var dynamicClaims = await cache.GetAsync(userId.Value, identity.FindTenantId());
foreach (var claim in dynamicClaims)
{
identity.AddOrReplace(new Claim(claim.Type, claim.Value));
}
var dynamicClaimsCache = context.GetRequiredService<IdentityDynamicClaimsPrincipalContributorCache>();
var dynamicClaims = await dynamicClaimsCache.GetAsync(userId.Value, identity.FindTenantId());
await MapCommonClaimsAsync(identity, dynamicClaims);
await AddDynamicClaims(identity, dynamicClaims);
}
}

@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
@ -16,45 +16,64 @@ namespace Volo.Abp.Identity;
public class IdentityDynamicClaimsPrincipalContributorCache : ITransientDependency
{
protected IServiceProvider ServiceProvider { get; }
public ILogger<IdentityDynamicClaimsPrincipalContributorCache> Logger { get; set; }
public IdentityDynamicClaimsPrincipalContributorCache(IServiceProvider serviceProvider)
protected IDistributedCache<List<AbpClaimCacheItem>> Cache { get; }
protected ICurrentTenant CurrentTenant { get; }
protected IdentityUserManager UserManager { get; }
protected IUserClaimsPrincipalFactory<IdentityUser> UserClaimsPrincipalFactory { get; }
protected IOptions<AbpClaimsPrincipalFactoryOptions> AbpClaimsPrincipalFactoryOptions { get; }
protected IOptions<IdentityDynamicClaimsPrincipalContributorCacheOptions> CacheOptions { get; }
public IdentityDynamicClaimsPrincipalContributorCache(
IDistributedCache<List<AbpClaimCacheItem>> cache,
ICurrentTenant currentTenant,
IdentityUserManager userManager,
IUserClaimsPrincipalFactory<IdentityUser> userClaimsPrincipalFactory,
IOptions<AbpClaimsPrincipalFactoryOptions> abpClaimsPrincipalFactoryOptions,
IOptions<IdentityDynamicClaimsPrincipalContributorCacheOptions> cacheOptions)
{
this.ServiceProvider = serviceProvider;
Cache = cache;
CurrentTenant = currentTenant;
UserManager = userManager;
UserClaimsPrincipalFactory = userClaimsPrincipalFactory;
AbpClaimsPrincipalFactoryOptions = abpClaimsPrincipalFactoryOptions;
CacheOptions = cacheOptions;
Logger = NullLogger<IdentityDynamicClaimsPrincipalContributorCache>.Instance;
}
public virtual async Task<List<AbpClaimCacheItem>> GetAsync(Guid userId, Guid? tenantId = null)
{
var logger = ServiceProvider.GetRequiredService<ILogger<IdentityDynamicClaimsPrincipalContributorCache>>();
logger.LogDebug($"Get dynamic claims cache for user: {userId}");
var cache = ServiceProvider.GetRequiredService<IDistributedCache<List<AbpClaimCacheItem>>>();
Logger.LogDebug($"Get dynamic claims cache for user: {userId}");
return await cache.GetOrAddAsync($"{nameof(IdentityDynamicClaimsPrincipalContributorCache)}_{tenantId}_{userId}", async () =>
return await Cache.GetOrAddAsync(AbpClaimCacheItem.CalculateCacheKey(userId, tenantId), async () =>
{
using (ServiceProvider.GetRequiredService<ICurrentTenant>().Change(tenantId))
using (CurrentTenant.Change(tenantId))
{
logger.LogDebug($"Filling dynamic claims cache for user: {userId}");
var userManager = ServiceProvider.GetRequiredService<IdentityUserManager>();
var user = await userManager.FindByIdAsync(userId.ToString());
Logger.LogDebug($"Filling dynamic claims cache for user: {userId}");
var user = await UserManager.FindByIdAsync(userId.ToString());
if (user == null)
{
logger.LogWarning($"User not found: {userId}");
Logger.LogWarning($"User not found: {userId}");
return new List<AbpClaimCacheItem>();
}
var factory = ServiceProvider.GetRequiredService<IUserClaimsPrincipalFactory<IdentityUser>>();
var principal = await factory.CreateAsync(user);
var options = ServiceProvider.GetRequiredService<IOptions<AbpClaimsPrincipalFactoryOptions>>().Value;
return principal.Identities.FirstOrDefault()?.Claims.Where(c => options.DynamicClaims.Contains(c.Type)).Select(c => new AbpClaimCacheItem(c.Type, c.Value)).ToList();
var principal = await UserClaimsPrincipalFactory.CreateAsync(user);
return principal.Identities.FirstOrDefault()?.Claims
.Where(c => AbpClaimsPrincipalFactoryOptions.Value.DynamicClaims.Contains(c.Type))
.Select(c => new AbpClaimCacheItem(c.Type, c.Value)).ToList();
}
}, () => new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = CacheOptions.Value.CacheAbsoluteExpiration
});
}
public virtual async Task ClearAsync(Guid userId, Guid? tenantId = null)
{
var cache = ServiceProvider.GetRequiredService<IDistributedCache<List<AbpClaimCacheItem>>>();
var logger = ServiceProvider.GetRequiredService<ILogger<IdentityDynamicClaimsPrincipalContributorCache>>();
logger.LogDebug($"Clearing dynamic claims cache for user: {userId}");
await cache.RemoveAsync($"{nameof(IdentityDynamicClaimsPrincipalContributorCache)}_{tenantId}_{userId}");
Logger.LogDebug($"Clearing dynamic claims cache for user: {userId}");
await Cache.RemoveAsync(AbpClaimCacheItem.CalculateCacheKey(userId, tenantId));
}
}

@ -0,0 +1,13 @@
using System;
namespace Volo.Abp.Identity;
public class IdentityDynamicClaimsPrincipalContributorCacheOptions
{
public TimeSpan CacheAbsoluteExpiration { get; set; }
public IdentityDynamicClaimsPrincipalContributorCacheOptions()
{
CacheAbsoluteExpiration = TimeSpan.FromHours(1);
}
}

@ -2,6 +2,7 @@
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Security.Claims;
@ -22,6 +23,11 @@ public class AbpUserClaimsPrincipalFactory_Tests : AbpIdentityDomainTestBase
_testData = GetRequiredService<IdentityTestData>();
}
protected override void AfterAddApplication(IServiceCollection services)
{
services.AddTransient<TestAbpClaimsPrincipalContributor>();
}
[Fact]
public async Task Add_And_Replace_Claims_Test()
{
@ -42,7 +48,7 @@ public class AbpUserClaimsPrincipalFactory_Tests : AbpIdentityDomainTestBase
});
}
class TestAbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency
class TestAbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor
{
//https://github.com/dotnet/aspnetcore/blob/v5.0.0/src/Identity/Extensions.Core/src/UserClaimsPrincipalFactory.cs#L79
private static string IdentityAuthenticationType => "Identity.Application";

@ -211,6 +211,7 @@ public class MyProjectNameAuthServerModule : AbpModule
app.UseMultiTenancy();
}
app.UseDynamicClaims();
app.UseUnitOfWork();
app.UseAuthorization();
app.UseAuditing();

@ -44,6 +44,7 @@ using Volo.Abp.Identity.Blazor.Server;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Security.Claims;
using Volo.Abp.SettingManagement.Blazor.Server;
using Volo.Abp.Swashbuckle;
using Volo.Abp.TenantManagement.Blazor.Server;
@ -188,7 +189,7 @@ public class MyProjectNameBlazorModule : AbpModule
* This configuration is used when the AuthServer is running on the internal network such as docker or k8s.
* Configuring the redirecting URLs for internal network and the web
* The login and the logout URLs are configured to redirect to the AuthServer real DNS for browser.
* The token acquired and validated from the the internal network AuthServer URL.
* The token acquired and validated from the the internal network AuthServer URL.
*/
if (configuration.GetValue<bool>("AuthServer:IsContainerized"))
{
@ -227,6 +228,11 @@ public class MyProjectNameBlazorModule : AbpModule
};
});
}
context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
{
options.RemoteUrl = configuration["AuthServer:Authority"] + options.RemoteUrl;
});
}
private void ConfigureVirtualFileSystem(IWebHostEnvironment hostingEnvironment)
@ -351,7 +357,7 @@ public class MyProjectNameBlazorModule : AbpModule
{
app.UseMultiTenancy();
}
app.UseDynamicClaims();
app.UseAuthorization();
app.UseSwagger();
app.UseAbpSwaggerUI(options =>

@ -265,7 +265,7 @@ public class MyProjectNameBlazorModule : AbpModule
{
app.UseMultiTenancy();
}
app.UseDynamicClaims();
app.UseUnitOfWork();
app.UseAuthorization();
app.UseSwagger();

@ -15,6 +15,7 @@ using Volo.Abp.AutoMapper;
using Volo.Abp.Modularity;
using Volo.Abp.UI.Navigation;
using Volo.Abp.Identity.Blazor.WebAssembly;
using Volo.Abp.Security.Claims;
using Volo.Abp.SettingManagement.Blazor.WebAssembly;
using Volo.Abp.TenantManagement.Blazor.WebAssembly;
@ -80,6 +81,11 @@ public class MyProjectNameBlazorModule : AbpModule
options.ProviderOptions.DefaultScopes.Add("email");
options.ProviderOptions.DefaultScopes.Add("phone");
});
builder.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
{
options.RemoteUrl = builder.Configuration["AuthServer:Authority"] + options.RemoteUrl;
});
}
private static void ConfigureUI(WebAssemblyHostBuilder builder)

@ -24,6 +24,7 @@ using Volo.Abp.Autofac;
using Volo.Abp.Caching;
using Volo.Abp.Caching.StackExchangeRedis;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Identity;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.Swashbuckle;
@ -188,7 +189,7 @@ public class MyProjectNameHttpApiHostModule : AbpModule
{
app.UseMultiTenancy();
}
app.UseDynamicClaims();
app.UseAuthorization();
app.UseSwagger();

@ -198,7 +198,7 @@ public class MyProjectNameHttpApiHostModule : AbpModule
{
app.UseMultiTenancy();
}
app.UseDynamicClaims();
app.UseUnitOfWork();
app.UseAuthorization();

@ -20,6 +20,7 @@
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="8.0.0-rc.2.*" />
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="7.0.3" />
</ItemGroup>
<ItemGroup>

@ -38,6 +38,7 @@ using Volo.Abp.Identity.Web;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.PermissionManagement.Web;
using Volo.Abp.Security.Claims;
using Volo.Abp.SettingManagement.Web;
using Volo.Abp.Swashbuckle;
using Volo.Abp.TenantManagement.Web;
@ -170,7 +171,7 @@ public class MyProjectNameWebModule : AbpModule
* This configuration is used when the AuthServer is running on the internal network such as docker or k8s.
* Configuring the redirecting URLs for internal network and the web
* The login and the logout URLs are configured to redirect to the AuthServer real DNS for browser.
* The token acquired and validated from the the internal network AuthServer URL.
* The token acquired and validated from the the internal network AuthServer URL.
*/
if (configuration.GetValue<bool>("AuthServer:IsContainerized"))
{
@ -209,6 +210,11 @@ public class MyProjectNameWebModule : AbpModule
};
});
}
context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
{
options.RemoteUrl = configuration["AuthServer:Authority"] + options.RemoteUrl;
});
}
private void ConfigureAutoMapper()
@ -316,6 +322,7 @@ public class MyProjectNameWebModule : AbpModule
app.UseMultiTenancy();
}
app.UseDynamicClaims();
app.UseAuthorization();
app.UseSwagger();
app.UseAbpSwaggerUI(options =>

@ -230,6 +230,7 @@ public class MyProjectNameWebModule : AbpModule
app.UseMultiTenancy();
}
app.UseDynamicClaims();
app.UseUnitOfWork();
app.UseAuthorization();
app.UseSwagger();

Loading…
Cancel
Save