mirror of https://github.com/abpframework/abp
parent
9a1b4c2004
commit
02bd699c4d
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
{
|
||||
|
||||
}
|
@ -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
|
||||
{
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace Volo.Abp.Identity;
|
||||
|
||||
public class IdentityDynamicClaimsPrincipalContributorCacheOptions
|
||||
{
|
||||
public TimeSpan CacheAbsoluteExpiration { get; set; }
|
||||
|
||||
public IdentityDynamicClaimsPrincipalContributorCacheOptions()
|
||||
{
|
||||
CacheAbsoluteExpiration = TimeSpan.FromHours(1);
|
||||
}
|
||||
}
|
Loading…
Reference in new issue