Skip to content

Dynamic claims problem #24820

@ArcherTrister

Description

@ArcherTrister

Is there an existing issue for this?

  • I have searched the existing issues

Description

  1. The document description is not clear, it is not enough to just set IsEnabled to true in the WebRemoteDynamicClaimsPrincipalributorOptions configuration, WebRemoteDynamicClaimsPrincipalContributor will not be triggered.

  2. Even if WebRemoteDynamicClaimsPrincipalContributor is triggered, RemoteRefreshUrl will not be called because they use the same cache, IdentityDynamicClaimsPrincipalContributor is triggered preferentially, and WebRemoteDynamicClaimsPrincipalContributor returns the cached value directly after it is triggered, thus not requesting RemoteRefreshUrl.

  3. Missing other services custom claim.

Reproduction Steps

  1. Add a dependency on the AbpAspNetCoreAuthenticationJwtBearerModule module in the xxxModule and add the following configuration to enable the WebRemoteDynamicsPrincipalContributor.
[DependsOn(typeof(AbpAspNetCoreAuthenticationJwtBearerModule))]
public class xxxModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        PreConfigure<WebRemoteDynamicClaimsPrincipalContributorOptions>(options =>
        {
            options.IsEnabled = true;
        });
    }
}
  1. Rewrite the AbpClaimsPrincipalFactory service, without changing the actual logic, just to add log printing
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IAbpClaimsPrincipalFactory), typeof(AbpClaimsPrincipalFactory), typeof(CustomClaimsPrincipalFactory))]
public class CustomClaimsPrincipalFactory : AbpClaimsPrincipalFactory
{
    public CustomClaimsPrincipalFactory(IServiceProvider serviceProvider, IOptions<AbpClaimsPrincipalFactoryOptions> abpClaimOptions) : base(serviceProvider, abpClaimOptions)
    {
    }

    public override async Task<ClaimsPrincipal> InternalCreateAsync(AbpClaimsPrincipalFactoryOptions options, ClaimsPrincipal? existsClaimsPrincipal = null, bool isDynamic = false)
    {
        var claimsPrincipal = existsClaimsPrincipal ?? new ClaimsPrincipal(new ClaimsIdentity(
            AuthenticationType,
            AbpClaimTypes.UserName,
            AbpClaimTypes.Role));

        var context = new AbpClaimsPrincipalContributorContext(claimsPrincipal, ServiceProvider);
        var logger = ServiceProvider.GetRequiredService<ILogger<CustomClaimsPrincipalFactory>>();

        if (!isDynamic)
        {
            logger.LogInformation("DynamicClaims no enabled");
            foreach (var contributorType in options.Contributors)
            {
                logger.LogInformation("Contributor call {ContributorName}", contributorType.Name);
                var contributor = (IAbpClaimsPrincipalContributor)ServiceProvider.GetRequiredService(contributorType);
                await contributor.ContributeAsync(context);
            }
        }
        else
        {
            logger.LogInformation("DynamicClaims enabled");
            foreach (var contributorType in options.DynamicContributors)
            {
                logger.LogInformation("DynamicContributor call {DynamicContributorName}", contributorType.Name);
                var contributor = (IAbpDynamicClaimsPrincipalContributor)ServiceProvider.GetRequiredService(contributorType);
                await contributor.ContributeAsync(context);
            }
        }

        return context.ClaimsPrincipal;
    }
}
  1. Rewrite the DynamicClaimsAppService service, again just add logs
[RemoteService(IsEnabled = false)]
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IDynamicClaimsAppService), typeof(DynamicClaimsAppService), typeof(CustomDynamicClaimsAppService))]
public class CustomDynamicClaimsAppService : DynamicClaimsAppService
{
    public CustomDynamicClaimsAppService(IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache, IAbpClaimsPrincipalFactory abpClaimsPrincipalFactory, ICurrentPrincipalAccessor principalAccessor) : base(identityDynamicClaimsPrincipalContributorCache, abpClaimsPrincipalFactory, principalAccessor)
    {
    }
    
    [Authorize]
    public override async Task RefreshAsync()
    {
        Logger.LogInformation("Trigger dynamicClaims refresh");
        await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(CurrentUser.GetId(), CurrentUser.TenantId);
        await AbpClaimsPrincipalFactory.CreateDynamicAsync(PrincipalAccessor.Principal);
    }
}
  1. Login and call the web api, observe the log print

Expected behavior

WebRemoteDynamicClaimsPrincipalContributor should be triggered and log should be printed.

Actual behavior

Image

Dynamic claims are only triggered when IdentityDynamicClaimsPrincipalContributor is triggered and OpenIddictClaimsPrincipalContributor is also triggered.

The configuration of AbpClaimsPrincipalFactoryOptions is also needed to be added in the PreConfigureServices method in order to truly enable the WebRemoteDynamicsPrincipalContributor.

[DependsOn(typeof(AbpAspNetCoreAuthenticationJwtBearerModule))]
public class xxxModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        PreConfigure<WebRemoteDynamicClaimsPrincipalContributorOptions>(options =>
        {
            options.IsEnabled = true;
        });

        PreConfigure<AbpClaimsPrincipalFactoryOptions>(options =>
        {
            options.IsRemoteRefreshEnabled = true;
        });
    }
}
Image

Because IdentityDynamicClaimsPrincipalContributor is triggered preferentially the CreateAsync method of UserClaimsPrincipalFactory is called in the IdentityDynamicClaimsPrincipalContributorCache and the cache of the dynamic claims is set, this is also why the OpenIddictClaimsPrincipalContributor is triggered.
When the WebRemoteDynamicClaimsPrincipalContributor is triggered, cache is not empty, so it returns directly, which also causes the loss of custom claims added by other services.

Regression?

The same is true in version 9.x.

Known Workarounds

No response

Version

10.0.2

User Interface

Common (Default)

Database Provider

EF Core (Default)

Tiered or separate authentication server

Separate Auth Server

Operation System

macOS

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions