web-dev-qa-db-ja.com

.NET Core 2.2から3.1に移行した後、ASP.NET CoreカスタムAuthenticationHandlerでAllowAnonymousが無視されない

基本認証で動作していたASP.NET Core 2.2 Web APIがあります。これまでのところ問題なく動作し、問題はありません。コントローラーの1つで、1つのアクションメソッドが[AllowAnonymous]を使用して、通常どおりユーザーログインを行います。

[Produces("application/json")]
[Route("user")]
[AllowAnonymous]
[ApiController]
public class LoginController : ControllerBase
{
    private readonly IConfiguration _configuration;
    private readonly IMessagingService _messageService;
    private readonly IBasicAuthenticationService _basicAuthenticationService;
    private readonly string PWAPIBaseUrl;

    public LoginController(IConfiguration configuration, ILogger<LoginController> logger, IMessagingService messagingService, IBasicAuthenticationService authenticationService)
    {
        _configuration = configuration;
        _logger = logger;
        _messageService = messagingService;
        _basicAuthenticationService = authenticationService;
    }

    [HttpGet]
    [AllowAnonymous]
    [Route("login/{username}/{clientID}")]
    public async Task<IActionResult> UserLogin(string username, string clientID)
    {
        // Check the Credentials Manually
        string failReason = "";
        if (!CheckCredentials(out failReason))
        {
            return StatusCode(StatusCodes.Status403Forbidden, userInfo);
        }

        // Load the Roles and UI Preferences ...

    }
}

.NET Core 2.2の終わりが近づいているので、.NET Core 3.1へのアップグレードを試みて 公式の移行ガイド に従い、必要な 変更 を作成しました。アプリケーションはスムーズに起動しましたが、アップグレードを禁止するバグの問題が1つあります。

上記のコントローラーでは、[AllowAnonymous]は無視されず、認証が評価されてエラーでスローされます。しかし、後にLoginメソッドが実行されます。これにより、すべての依存アプリケーションでログインが中断します。 thisthis および this のようなStackoverflowからの提案をすべて試しました。

基本認証ハンドラー:

public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    private readonly ILogger<BasicAuthenticationHandler> _logger = null;
    private readonly IBasicAuthenticationService _basicAuthenticationService;

    public BasicAuthenticationHandler(
        IOptionsMonitor<AuthenticationSchemeOptions> options,
        UrlEncoder encoder,
        ILoggerFactory loggerFactory,
        ISystemClock clock,
        IBasicAuthenticationService authenticationService)
        : base(options, loggerFactory, encoder, clock)
    {
        _logger = loggerFactory.CreateLogger<BasicAuthenticationHandler>();
        _basicAuthenticationService = authenticationService;
    }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var config = Util.GetConfig();

        if (!Request.Headers.ContainsKey("Authorization"))
        {
            _logger.LogError("No authorization credentials");
            return AuthenticateResult.NoResult();
        }

        if (!Request.Headers.ContainsKey("ClientID"))
        {
            _logger.LogError("Missing header client token");
            return AuthenticateResult.Fail("Missing header client token");
        }

        var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
        if (authHeader.Scheme != "Basic")
        {
            _logger.LogError("Authentication scheme not recognized");
            return AuthenticateResult.Fail("Authentication scheme not recognized");
        }

        var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
        var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':');
        var username = credentials[0];
        var password = credentials[1];

        string fullname = "";
        string failReason = "";
        bool t = false;

        IPrincipal principal = null;

        // Do Business Validation against the DB

        if (!t) // login failed
        {
            byte[] bEncodedResponse = Encoding.UTF8.GetBytes(failReason);
            await Context.Response.Body.WriteAsync(bEncodedResponse, 0, bEncodedResponse.Length);
            return AuthenticateResult.Fail(failReason);
        }
        else
        {
            var claims = new[]
            {
                new Claim(ClaimTypes.NameIdentifier, username),
                new Claim(ClaimTypes.Name, fullname),
            };

            var identity = new ClaimsIdentity(claims, Scheme.Name);
            principal = principal==null?new ClaimsPrincipal(identity): principal;
            var ticket = new AuthenticationTicket(principal as ClaimsPrincipal, Scheme.Name);

            return AuthenticateResult.Success(ticket);
        }
    }


}

Startup.cs

public class Startup
{
    public Startup(IWebHostEnvironment environment, IConfiguration configuration, ILoggerFactory loggerFactory)
    {
        Environment = environment;
        Configuration = configuration;
        LoggerFactory = loggerFactory;
    }

    public IConfiguration Configuration { get; }
    public ILoggerFactory LoggerFactory { get; }
    public IWebHostEnvironment Environment { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication("BasicAuthentication")
            .AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);

        // Adding the Configuration Options -- Extension Methods to Inject Configuration as IOption POCOs
        services.ConfigureAPIOptions(Configuration);

        // configure DI for application services -- Other DI Objects
        services.ConfigureDependencies(Configuration, LoggerFactory);

        Common.APIConfiguration.Current = Configuration;

        services.AddControllers();
        services.AddAuthorization();
        if (Environment.IsDevelopment())
        {
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo { Title = "My Materials API", Version = "v1" });
            });
        }
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseRouting();

        if (env.IsDevelopment())
        {
            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "My Materials API v1");
                c.RoutePrefix = string.Empty;
            });
        }

        app.UseAuthentication();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

私はまだ私が間違ったことに無知であり、ASP.NET Core 3.1で何かが欠けている可能性があります。これを機能させるために私を助けてください。前もって感謝します。

EDIT 1:

ServiceExtensions.cs

public static class ServiceExtensions
{
    public static void ConfigureAPIOptions(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddOptions();
        services.Configure<DataSetting>(configuration.GetSection("DataSettings"));
        services.Configure<UrlSetting>(configuration.GetSection("UrlSettings"));
        services.Configure<SiteSettings>(configuration.GetSection("SiteSettings"));
    }

    public static void ConfigureDependencies(this IServiceCollection services, IConfiguration configuration, ILoggerFactory loggerFactory)
    {
        services.AddSingleton<IConfiguration>(configuration);
        services.AddScoped<IBasicAuthenticationService, BasicAuthenticationService>();
        services.AddScoped<IMessagingService>(s => new MessagingServices(configuration, loggerFactory.CreateLogger<MessagingServices>()));
        services.AddHostedService<TimedHostedService>();
    }
}

DIが不可能な構成にアクセスするための小さなクラッジ。

public static class APIConfiguration
{
    public static IConfiguration Current { get; set; }
}
3
Sathish Guru V

私はこれを試しました、そしてそれは本当に私を助けます。

private static bool HasAllowAnonymous(AuthorizationFilterContext context)
    {
        var filters = context.Filters;
        for (var i = 0; i < filters.Count; i++)
        {
            if (filters[i] is IAllowAnonymousFilter)
            {
                return true;
            }
        }

        // When doing endpoint routing, MVC does not add AllowAnonymousFilters for AllowAnonymousAttributes that
        // were discovered on controllers and actions. To maintain compat with 2.x,
        // we'll check for the presence of IAllowAnonymous in endpoint metadata.
        var endpoint = context.HttpContext.GetEndpoint();
        if (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null)
        {
            return true;
        }

        return false;
    }

https://github.com/dotnet/aspnetcore/blob/bd65275148abc9b07a3b59797a88d485341152bf/src/Mvc/Mvc.Core/src/Authorization/AuthorizeFilter.cs#L236

それはここで言及されました https://docs.Microsoft.com/en-us/dotnet/core/compatibility/2.2-3.1#authorization-iallowanonymous-removed-from-authorizationfiltercontextfilters

3
haiduong87