mirror of https://github.com/abpframework/abp.git
committed by
GitHub
18 changed files with 318 additions and 68 deletions
@ -0,0 +1,36 @@ |
|||
using System; |
|||
using Microsoft.AspNetCore.Authentication; |
|||
using Microsoft.AspNetCore.Authentication.JwtBearer; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection; |
|||
|
|||
public static class AbpJwtBearerExtensions |
|||
{ |
|||
public static AuthenticationBuilder AddAbpJwtBearer(this AuthenticationBuilder builder) |
|||
=> builder.AddAbpJwtBearer(JwtBearerDefaults.AuthenticationScheme, _ => { }); |
|||
|
|||
public static AuthenticationBuilder AddAbpJwtBearer(this AuthenticationBuilder builder, Action<JwtBearerOptions> configureOptions) |
|||
=> builder.AddAbpJwtBearer(JwtBearerDefaults.AuthenticationScheme, configureOptions); |
|||
|
|||
public static AuthenticationBuilder AddAbpJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, Action<JwtBearerOptions> configureOptions) |
|||
=> builder.AddAbpJwtBearer(authenticationScheme, "Bearer", configureOptions); |
|||
|
|||
public static AuthenticationBuilder AddAbpJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<JwtBearerOptions> configureOptions) |
|||
{ |
|||
builder.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options => |
|||
{ |
|||
var jwtBearerOption = new JwtBearerOptions(); |
|||
configureOptions?.Invoke(jwtBearerOption); |
|||
if (!jwtBearerOption.Authority.IsNullOrEmpty()) |
|||
{ |
|||
options.RemoteRefreshUrl = jwtBearerOption.Authority.RemovePostFix("/") + options.RemoteRefreshUrl; |
|||
} |
|||
}); |
|||
|
|||
return builder.AddJwtBearer(authenticationScheme, displayName, options => |
|||
{ |
|||
configureOptions?.Invoke(options); |
|||
}); |
|||
} |
|||
} |
|||
@ -1,10 +1,24 @@ |
|||
using Volo.Abp.Modularity; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.AspNetCore.Authentication.JwtBearer.DynamicClaims; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.Security; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Authentication.JwtBearer; |
|||
|
|||
[DependsOn(typeof(AbpSecurityModule))] |
|||
[DependsOn(typeof(AbpSecurityModule), typeof(AbpCachingModule))] |
|||
public class AbpAspNetCoreAuthenticationJwtBearerModule : 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,44 +1,10 @@ |
|||
using System; |
|||
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; |
|||
} |
|||
|
|||
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<TContributorCache>().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) |
|||
{ |
|||
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