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 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<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