mirror of https://github.com/abpframework/abp.git
16 changed files with 294 additions and 70 deletions
@ -0,0 +1,27 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net8.0</TargetFramework> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<WarningsAsErrors>Nullable</WarningsAsErrors> |
||||
|
<PackageId>Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims</PackageId> |
||||
|
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Volo.Abp.AspNetCore.Authentication.JwtBearer\Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj" /> |
||||
|
<ProjectReference Include="..\Volo.Abp.Caching\Volo.Abp.Caching.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="IdentityModel" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -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<AbpClaimsPrincipalFactoryOptions>(); |
||||
|
if (abpClaimsPrincipalFactoryOptions.IsRemoteRefreshEnabled) |
||||
|
{ |
||||
|
context.Services.AddTransient<WebRemoteDynamicClaimsPrincipalContributor>(); |
||||
|
context.Services.AddTransient<WebRemoteDynamicClaimsPrincipalContributorCache>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<WebRemoteDynamicClaimsPrincipalContributor, WebRemoteDynamicClaimsPrincipalContributorCache> |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -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<WebRemoteDynamicClaimsPrincipalContributorCache> |
||||
|
{ |
||||
|
public const string HttpClientName = nameof(WebRemoteDynamicClaimsPrincipalContributorCache); |
||||
|
|
||||
|
protected IDistributedCache<AbpDynamicClaimCacheItem> Cache { get; } |
||||
|
protected IHttpClientFactory HttpClientFactory { get; } |
||||
|
protected IHttpContextAccessor HttpContextAccessor { get; } |
||||
|
protected IOptions<WebRemoteDynamicClaimsPrincipalContributorOptions> Options { get; } |
||||
|
|
||||
|
public WebRemoteDynamicClaimsPrincipalContributorCache( |
||||
|
IDistributedCache<AbpDynamicClaimCacheItem> cache, |
||||
|
IHttpClientFactory httpClientFactory, |
||||
|
IOptions<AbpClaimsPrincipalFactoryOptions> abpClaimsPrincipalFactoryOptions, |
||||
|
IHttpContextAccessor httpContextAccessor, |
||||
|
IOptions<WebRemoteDynamicClaimsPrincipalContributorOptions> options) |
||||
|
: base(abpClaimsPrincipalFactoryOptions) |
||||
|
{ |
||||
|
Cache = cache; |
||||
|
HttpClientFactory = httpClientFactory; |
||||
|
HttpContextAccessor = httpContextAccessor; |
||||
|
Options = options; |
||||
|
} |
||||
|
|
||||
|
protected async override Task<AbpDynamicClaimCacheItem?> 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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -1,50 +1,10 @@ |
|||||
using System; |
using Volo.Abp.DependencyInjection; |
||||
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.Security.Claims; |
using Volo.Abp.Security.Claims; |
||||
|
|
||||
namespace Volo.Abp.AspNetCore.Mvc.Client; |
namespace Volo.Abp.AspNetCore.Mvc.Client; |
||||
|
|
||||
public class RemoteDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincipalContributorBase |
[DisableConventionalRegistration] |
||||
|
public class RemoteDynamicClaimsPrincipalContributor : RemoteDynamicClaimsPrincipalContributorBase<RemoteDynamicClaimsPrincipalContributor, RemoteDynamicClaimsPrincipalContributorCache> |
||||
{ |
{ |
||||
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>(); |
|
||||
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<ILogger<RemoteDynamicClaimsPrincipalContributor>>(); |
|
||||
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); |
|
||||
} |
|
||||
} |
} |
||||
|
|||||
@ -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<TContributor, TContributorCache> : AbpDynamicClaimsPrincipalContributorBase |
||||
|
where TContributor : class |
||||
|
where TContributorCache : RemoteDynamicClaimsPrincipalContributorCacheBase<TContributorCache> |
||||
|
{ |
||||
|
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<TContributor>().As<TContributorCache>(); |
||||
|
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<ILogger<TContributor>>(); |
||||
|
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); |
||||
|
} |
||||
|
} |
||||
@ -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<TContributorCache> |
||||
|
{ |
||||
|
public ILogger<TContributorCache> Logger { get; set; } |
||||
|
|
||||
|
protected IOptions<AbpClaimsPrincipalFactoryOptions> AbpClaimsPrincipalFactoryOptions { get; } |
||||
|
|
||||
|
protected RemoteDynamicClaimsPrincipalContributorCacheBase(IOptions<AbpClaimsPrincipalFactoryOptions> abpClaimsPrincipalFactoryOptions) |
||||
|
{ |
||||
|
AbpClaimsPrincipalFactoryOptions = abpClaimsPrincipalFactoryOptions; |
||||
|
|
||||
|
Logger = NullLogger<TContributorCache>.Instance; |
||||
|
} |
||||
|
|
||||
|
public async Task<AbpDynamicClaimCacheItem> 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<AbpDynamicClaimCacheItem?> GetCacheAsync(Guid userId, Guid? tenantId = null); |
||||
|
|
||||
|
protected abstract Task RefreshAsync(Guid userId, Guid? tenantId = null); |
||||
|
} |
||||
Loading…
Reference in new issue