使用多种身份验证方案时出现 NullReferenceException

问题描述 投票:0回答:1

根据配置的身份验证方式,JWT 或 AzureAD 身份验证适用于受保护的端点,但其中之一总是失败,并显示 “对象引用未设置到对象的实例”

我已经像这样配置了我的 dotnet core Web api:

services
      .AddAuthentication(options =>
             {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
             })
          .AddJwtBearer(options =>
          {
              var jwtSettings = config.GetSection("SecuritySettings:JwtSettings").Get<JwtSettings>();
              byte[] key = Encoding.ASCII.GetBytes(jwtSettings.Key);

              options.RequireHttpsMetadata = false;
              options.SaveToken = true;
              options.TokenValidationParameters = new TokenValidationParameters
              {
                  ValidateIssuerSigningKey = true,
                  IssuerSigningKey = new SymmetricSecurityKey(key),
                  ValidateIssuer = false,
                  ValidateLifetime = true,
                  ValidateAudience = false,
                  RoleClaimType = ClaimTypes.Role,
                  ClockSkew = TimeSpan.Zero
              };

              options.Events = new JwtBearerEvents
              {
                  OnChallenge = context =>
                  {
                      context.HandleResponse();
                      if (!context.Response.HasStarted)
                      {
                          throw new UnauthorizedException("Authentication Failed.");
                      }

                      return Task.CompletedTask;
                  },
                  OnForbidden = _ => throw new ForbiddenException("You are not authorized to access this resource."),
                  OnMessageReceived = context =>
                  {
                      var accessToken = context.Request.Query["access_token"];

                      if (!string.IsNullOrEmpty(accessToken) &&
                          context.HttpContext.Request.Path.StartsWithSegments("/notifications"))
                      {
                          // Read the token out of the query string
                          context.Token = accessToken;
                      }

                      return Task.CompletedTask;
                  }
              };
          })
          .AddMicrosoftIdentityWebApi(config, "SecuritySettings:AzureAd", "AzureAD");

      services.AddAuthorization(options =>
      {
          var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
              JwtBearerDefaults.AuthenticationScheme,
              "AzureAD");
          defaultAuthorizationPolicyBuilder =
              defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
          options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
      });

我打开了详细日志记录:

[14:36:59 DBG] Entity Framework Core 7.0.12 initialized 'ApplicationDbContext' using provider 'Npgsql.EntityFrameworkCore.PostgreSQL:7.0.11+c25a0d7b68d66c6ab3849a3b4333964faea6adc9' with options: SensitiveDataLoggingEnabled MigrationsAssembly=Migrators.PostgreSQL
[14:37:26 DBG] Compiling query expression:
'DbSet<ApplicationUser>()
    .Where(u => u.ObjectId == __objectId_0)
    .FirstOrDefault()'
[14:37:26 DBG] Generated query execution expression:
'queryContext =>
{
    queryContext.AddParameter(
        name: "__ef_filter__Id_0",
        value: (object)Invoke(queryContext => ((ApplicationDbContext)queryContext.Context).TenantInfo.Id, queryContext));
    return ShapedQueryCompilingExpressionVisitor.SingleOrDefaultAsync<ApplicationUser>(
        asyncEnumerable: new SingleQueryingEnumerable<ApplicationUser>(
            (RelationalQueryContext)queryContext,
            RelationalCommandCache.QueryExpression(
                Projection Mapping:
                    EmptyProjectionMember -> Dictionary<IProperty, int> { [Property: ApplicationUser.Id (string) Required PK AfterSave:Throw, 0], [Property: ApplicationUser.AccessFailedCount (int) Required, 1], [Property: ApplicationUser.AuthenticationType (AuthenticationType?), 2], [Property: ApplicationUser.ConcurrencyStamp (string) Concurrency, 3], [Property: ApplicationUser.Email (string) MaxLength(256), 4], [Property: ApplicationUser.EmailConfirmed (bool) Required, 5], [Property: ApplicationUser.FirstName (string), 6], [Property: ApplicationUser.ImageUrl (string), 7], [Property: ApplicationUser.IsActive (bool) Required, 8], [Property: ApplicationUser.LastName (string), 9], [Property: ApplicationUser.LockoutEnabled (bool) Required, 10], [Property: ApplicationUser.LockoutEnd (DateTimeOffset?), 11], [Property: ApplicationUser.NormalizedEmail (string) Index MaxLength(256), 12], [Property: ApplicationUser.NormalizedUserName (string) Index MaxLength(256), 13], [Property: ApplicationUser.ObjectId (string) MaxLength(256), 14], [Property: ApplicationUser.PasswordHash (string), 15], [Property: ApplicationUser.PhoneNumber (string), 16], [Property: ApplicationUser.PhoneNumberConfirmed (bool) Required, 17], [Property: ApplicationUser.RefreshToken (string), 18], [Property: ApplicationUser.RefreshTokenExpiryTime (DateTime) Required, 19], [Property: ApplicationUser.SecurityStamp (string), 20], [Property: ApplicationUser.TenantId (string) Required Index MaxLength(64), 21], [Property: ApplicationUser.TwoFactorEnabled (bool) Required, 22], [Property: ApplicationUser.UserName (string) MaxLength(256), 23] }
                SELECT TOP(1) u.Id, u.AccessFailedCount, u.AuthenticationType, u.ConcurrencyStamp, u.Email, u.EmailConfirmed, u.FirstName, u.ImageUrl, u.IsActive, u.LastName, u.LockoutEnabled, u.LockoutEnd, u.NormalizedEmail, u.NormalizedUserName, u.ObjectId, u.PasswordHash, u.PhoneNumber, u.PhoneNumberConfirmed, u.RefreshToken, u.RefreshTokenExpiryTime, u.SecurityStamp, u.TenantId, u.TwoFactorEnabled, u.UserName
                FROM Identity.Users AS u
                WHERE (u.TenantId == @__ef_filter__Id_0) && (u.ObjectId == @__objectId_0)),
            null,
            Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, ApplicationUser>,
            HIS.Simple.Sync.Infrastructure.Persistence.Context.ApplicationDbContext,
            False,
            False,
            True
        ),
        cancellationToken: queryContext.CancellationToken);
}'
[14:37:34 ERR] Exception occurred while processing message.
System.NullReferenceException: Object reference not set to an instance of an object.
   at lambda_method340(Closure, QueryContext)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.FirstOrDefaultAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at HIS.Simple.Sync.Infrastructure.Identity.UserService.GetOrCreateFromPrincipalAsync(ClaimsPrincipal principal) in C:\xxxxxxxxxx\src\Infrastructure\Identity\UserService.CreateUpdate.cs:line 31
   at HIS.Simple.Sync.Infrastructure.Auth.AzureAd.AzureAdJwtBearerEvents.TokenValidated(TokenValidatedContext context) in C:\xxxxxxxxxx\src\Infrastructure\Auth\AzureAd\AzureAdJwtBearerEvents.cs:line 92
   at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
[14:37:34 ERR] Authentication failed Exception: System.NullReferenceException: Object reference not set to an instance of an object.
   at lambda_method340(Closure, QueryContext)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.FirstOrDefaultAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at HIS.Simple.Sync.Infrastructure.Identity.UserService.GetOrCreateFromPrincipalAsync(ClaimsPrincipal principal) in Cxxxxxxxxxxsrc\Infrastructure\Identity\UserService.CreateUpdate.cs:line 31
   at HIS.Simple.Sync.Infrastructure.Auth.AzureAd.AzureAdJwtBearerEvents.TokenValidated(TokenValidatedContext context) in C:\xxxxxxxxxx\src\Infrastructure\Auth\AzureAd\AzureAdJwtBearerEvents.cs:line 92
   at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()

错误发生在 Microsoft.EntityFrameworkCore.Query.Internal QueryCompiler.cs 的 ExecuteAsync 函数中:

The exception thrown

移除另一个后,两者都可以工作。

在所述端点执行的查询:

public async Task<UserDetailsDto> GetAsync(string userId, CancellationToken cancellationToken)
{
    var user = await _userManager.FindByIdAsync(userId);

    _ = user ?? throw new NotFoundException(_t["User Not Found."]);

    return user.Adapt<UserDetailsDto>();
}

我也调试过这个,

_userManager
和userId都不为null。 我在这个项目中使用了 fullstack Hero Web api 模板。

我尝试过调试,我尝试过切换配置,我尝试过使用不同的配置,例如

AddOpenIdConnect
,但仍然是相同的错误。

身份验证应同时使用 JWT 和 AzureAD。我应该能够使用两者生成的令牌进行身份验证。

编辑:事实证明这是 Finbuckle.Multitenant 的问题,但是仍然需要帮助,因为我无法解决该问题。

c# asp.net-core asp.net-core-identity
1个回答
0
投票

事实证明,这是一个 Finbuckle.Multitenant 的问题,它不支持使用 .WithClaimStrategy 和多个身份验证方案。

我使用了自定义策略来解决这个问题:

public class MultiAuthClaimStrategy : IMultiTenantStrategy
{
    private readonly string _tenantKey;
    private readonly string[]? _authenticationSchemes;

    public MultiAuthClaimStrategy(string template)
        : this(template, null)
    {
    }

    public MultiAuthClaimStrategy(string template, string[]? authenticationSchemes)
    {
        if (string.IsNullOrWhiteSpace(template))
            throw new ArgumentException(nameof(template));

        _tenantKey = template;
        _authenticationSchemes = authenticationSchemes;
    }

    public async Task<string?> GetIdentifierAsync(object context)
    {
        if (!(context is HttpContext httpContext))
            throw new MultiTenantException(null, new ArgumentException($@"""{nameof(context)}"" type must be of type HttpContext", nameof(context)));

        if (httpContext.User.Identity is { IsAuthenticated: true })
            return httpContext.User.FindFirst(_tenantKey)?.Value;

        var schemeProvider = httpContext.RequestServices.GetRequiredService<IAuthenticationSchemeProvider>();

        if (_authenticationSchemes != null && _authenticationSchemes.Length > 0)
        {
            foreach (var schemeName in _authenticationSchemes)
            {
                var authScheme = (await schemeProvider.GetAllSchemesAsync()).FirstOrDefault(x => x.Name == schemeName);
                if (authScheme != null)
                {
                    var identifier = await AuthenticateAndRetrieveIdentifier(httpContext, authScheme);
                    if (identifier != null) return identifier;
                }
            }
        }
        else
        {
            var authScheme = await schemeProvider.GetDefaultAuthenticateSchemeAsync();
            if (authScheme != null)
            {
                var identifier = await AuthenticateAndRetrieveIdentifier(httpContext, authScheme);
                if (identifier != null) return identifier;
            }
        }

        return null;
    }

    private async Task<string?> AuthenticateAndRetrieveIdentifier(HttpContext httpContext, AuthenticationScheme authScheme)
    {
        var handler = (IAuthenticationHandler)ActivatorUtilities.CreateInstance(httpContext.RequestServices, authScheme.HandlerType);
        await handler.InitializeAsync(authScheme, httpContext);
        httpContext.Items[$"__tenant____bypass_validate_principal__"] = "true"; // Value doesn't matter.
        var handlerResult = await handler.AuthenticateAsync();
        httpContext.Items.Remove($"__tenant____bypass_validate_principal__");

        return handlerResult.Principal?.FindFirst(_tenantKey)?.Value;
    }
}

然后我将其添加为这样的策略:

.WithStrategy<MultiAuthClaimStrategy>(ServiceLifetime.Singleton, "myTenantKey", new[] { "AzureAd", BearerTokenDefaults.AuthenticationScheme })

然而,不是我找出了问题所在,而是 Github 上的 @achandlerwhite / AndrewTriesToCode,他是这个很棒的 .NET 多租户中间件库的所有者和维护者!

© www.soinside.com 2019 - 2024. All rights reserved.