简体   繁体   中英

Asp .net Core 3.1 transforming ClaimsIdentity with multiple AuthenticationScheme

I've faced a problem while implementing a couple of authorization schemes in ASP.Net Core application. Lets say they are declared like this in Startup.cs

public void ConfigureServices(IServiceCollection services)
{
      AuthenticationBuilder builder = services
                .AddAuthentication()
                .AddBasicAuthentication(o => { o.Realm = "MyRealm"; })
                .AddApiKeyAuthentication()
                .AddBearerToken(opt => { });
}

Each of these schemes provide its own implementation of AuthenticationHandler returning ClaimsIdentity if succeeded. But in each case the structure of claims are incosistent, ie ApiKeyAuthentication may return ClaimsIdentity with business-sensitive data stored in claim "api_service" while BearerTokenScheme will store it in a claim "sub", and I dont have control over this. So if I would like to use this information in a controller to associate some process with a service which have called my api method, I have to implement some complicated logic that would analyze current ClaimsIdentity , its auth scheme and set of claims. Instead I would like to implement some sort of tranformation of ClaimsIdentity into MyServiceClaimsIdentity which would expose claims in a handy way so I can utilize them easily in my Controllers code:

public class MyServiceClaimsIdentity: IIdentity
{
    private readonly ClaimsIdentity innerIdentity;
    public Guid? UserId {get; }
    public string UserName {get; }
    public string ServiceName {get; }

    public MyServiceClaimsIdentity(ClaimsIdentity identity)
    {
        this.innerIdentity = identity;
        TransformClaimsIntoProperties();
    }

    private void TransformClaimsIntoProperties()
    {
        ......
    }
}

I've tried to implement some sort of "transformative" AuthenticationHandler which would produce MyServiceClaimsIdentity after all other handlers would produce their ClaimsIdentity.

public class FinalAuthenticationHandler : AuthenticationHandler<FinalAuthenticationOptions>
    {
        public FinalAuthenticationHandler(
            IOptionsMonitor<FinalAuthenticationOptions> options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            ISystemClock clock)
            : base(options, logger, encoder, clock)
        {
        }

        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            if (!this.Context.User.Identity.IsAuthenticated)
            {
                return null;
            }

            var identity = new MyServiceClaimsIdentity(this.Context.User.Identity);
            var principal = new ClaimsPrincipal(identity);
            var ticket = new AuthenticationTicket(principal, this.Scheme.Name);
            return AuthenticateResult.Success(ticket);
        }
    }

Too bad at this point this.Context.User.Identity doesnt have any information of an user, so I'm confused where to put this tranformation logic or how would I get current ClaimsIdentity provided by other Handler in my FinalAuthenticationHandler . Any help would be appreciated.

Implementing IClaimsTransformation and registering it as a Singleton did the job just fine

internal sealed class ClaimsTransformation : IClaimsTransformation
    {
        private readonly IDictionary<string, IClaimsHandler> handlersMap;

        public ClaimsTransformation(IEnumerable<IClaimsHandler> handlers)
        {
            if (handlers == null)
            {
                throw new ArgumentNullException(nameof(handlers));
            }

            this.handlersMap = handlers.ToDictionary(t => t.SchemeName);
        }

        public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
        {
            if (!(principal.Identity is ClaimsIdentity claimsIdentity))
            {
                throw new InvalidOperationException($"Principal.Identity is of type {principal.Identity.GetType()}, expected ClaimsIdentity");
            }

            if (!this.handlersMap.TryGetValue(principal.Identity.AuthenticationType, out var handler))
            {
                throw new AuthenticationException($"Scheme of type {principal.Identity.AuthenticationType} is not supported");
            }

            var result = new ClaimsPrincipal(handler.Handle(claimsIdentity));
            return Task.FromResult(result);
        }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM