根据配置身份验证的方式,JWT或AzureAD身份验证在受保护的端点上工作,但其中一个总是失败,错误为"Object reference not set to an instance of an object".

我已经这样配置了我的DotNet核心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();
      });

我打开了详细日志(log)记录:

[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()

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

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都不是空的. 我在这个项目中使用了Full Stack HERO网络API模板.

我试过调试,我试过切换配置,我试过使用不同的配置,比如AddOpenIdConnect,但仍然有相同的错误.

身份验证应该结合使用JWT和AzureAD.我应该能够使用由其中之一生成的令牌来进行身份验证.

编辑:原来这是芬巴克的问题.多租户,但仍然需要帮助,因为我无法解决这个问题.

推荐答案

原来它是一个issue with 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,Github是这个了不起的.NET多租户中间件库的所有者和维护者!

Csharp相关问答推荐

在包含空项的列表上使用具有断言T的摘要表

在ASP.NET中为数据注释 Select 合适的语言

如何使嵌套for-loop更高效?

注册通用工厂的C# Dep注入

使用LINQ to XML获取元素值列表是不起作用的

C#中使用BouncyCastle计算CMac

将类移动到新命名空间后更新RavenDB Raven-Clr-Type

具有以接口为其类型的属性的接口;类指定接口的实现,但无效

依赖项注入、工厂方法和处置困境

.NET 6:如何防止系统生成的日志(log)?

MSI无法将快捷方式添加到启动文件夹

如何正确处置所有动态控件?

Blazor服务器项目中的Blazor/.NET 8/Web API不工作

ASP.NET MVC数据批注验证组复选框

如何从SignalR获取连接客户端的域

仅在ASP.NETCore应用程序中的附加单独端口上公开一组终结点

将列表转换为带有逗号分隔字符串形式的值的字典

为什么我不能在固定语句中使用外部函数?

SendInput无法在C#中正确模拟键盘

ASP.NET重新加载Kestrel SSL证书