Как настроить базовую аутентификацию для определенных действий в моем контроллере asp net core 3.1?

Я создаю webapi, используя asp.net core 3.1, и я хочу использовать базовую аутентификацию, которую я буду аутентифицировать в Active Directory.

Я создаю обработчик и службу аутентификации, но проблема в том, что когда я украшаю действие контроллера [Authorize], функция HandleAuthenticateAsync не вызывается при вызове действия контроллера (хотя вызывается конструктор обработчика). Вместо этого я просто получаю ответ 401:

GET https://localhost:44321/Test/RequiresAuthentication HTTP/1.1
Authorization: Basic ..........
User-Agent: PostmanRuntime/7.26.8
Accept: */*
Postman-Token: d58490e4-2707-4b75-9cfa-679509951860
Host: localhost:44321
Accept-Encoding: gzip, deflate, br
Connection: keep-alive



HTTP/1.1 401 Unauthorized
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Thu, 10 Dec 2020 16:31:16 GMT
Content-Length: 0

И если я вызываю действие, не имеющее атрибута [Authorize], вызывается функция HandleAuthenticateAsync, но действие выполняется и возвращает 200, даже если HandleAuthenticateAsync возвращает AuthenticateResult.Fail. Должно быть, я совершенно не понимаю, как это должно работать.

GET https://localhost:44321/Test/NoAuthenticationRequired HTTP/1.1
User-Agent: PostmanRuntime/7.26.8
Accept: */*
Postman-Token: 81dd4c2a-32b6-45b9-bb88-9c6093f3675e
Host: localhost:44321
Accept-Encoding: gzip, deflate, br
Connection: keep-alive


HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Vary: Accept-Encoding
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Thu, 10 Dec 2020 16:35:36 GMT
Content-Length: 3

Ok!

У меня есть контроллер с одним действием, на котором я хочу пройти аутентификацию, и другим, которого я не хочу:

[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{

    private readonly ILogger<TestController> _logger;

    public TestController(ILogger<TestController> logger)
    {
        _logger = logger;
    }

    [HttpGet("RequiresAuthentication")]
    [Authorize]
    public string RestrictedGet()
    {
        return "Ok!";
    }

    [HttpGet("NoAuthenticationRequired")]
    public string NonRestrictedGet()
    {
        return "Ok!";
    }
}

У меня есть обработчик аутентификации:

public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    private IBasicAuthenticationService _authService;

    public BasicAuthenticationHandler(
            IOptionsMonitor<AuthenticationSchemeOptions> options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            ISystemClock clock,
            IBasicAuthenticationService authService)
            : base(options, logger, encoder, clock)
    {
        ...
        _authService = authService;
    }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        // skip authentication if endpoint has [AllowAnonymous] attribute
        var endpoint = Context.GetEndpoint();

        if (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null)
        {
            return AuthenticateResult.NoResult();
        }

        if (!Request.Headers.ContainsKey("Authorization"))
        {
            return AuthenticateResult.Fail("Missing Authorization Header.");
        }

        IBasicAuthenticationServiceUser user = null;
        try
        {
            var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
            var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
            var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
            var username = credentials[0];
            var password = credentials[1];
            user = await _authService.Authenticate(username, password);
        }
        catch
        {
            return AuthenticateResult.Fail("Invalid Authorization Header.");
        }

        if (user == null)
        {
            return AuthenticateResult.Fail("Invalid Username or Password.");
        }

        var claims = new[] {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.UserName),
            };

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

        return AuthenticateResult.Success(ticket);
    }
}

У меня есть служба аутентификации, которая аутентифицируется в активном каталоге:

class BasicAuthenticationActiveDirectoryService : IBasicAuthenticationService
{
    private class User : IBasicAuthenticationServiceUser
    {
        public string Id { get; set; }
        public string UserName { get; set; }
        public string Lastname { get; set; }
        public string FirstName { get; set; }
        public string Email { get; set; }
    }

    public async Task<IBasicAuthenticationServiceUser> Authenticate(string username, string password)
    {
        string domain = GetDomain(username);

        using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, domain))
        {
            // validate the credentials
            bool isValid = pc.ValidateCredentials(username, password);

            if (isValid)
            {
                User user = new User()
                {
                    Id = username,
                    UserName = username
                };

                UserPrincipal up = UserPrincipal.FindByIdentity(pc, username);

                user.FirstName = up.GivenName;
                user.Lastname = up.Surname;
                user.Email = up.EmailAddress;

                return user;
            }
            else
            {
                return null;
            }
        }
    }

    private string GetDomain(string username)
    {
        if (string.IsNullOrEmpty(username))
        {
            throw new ArgumentNullException(nameof(username), "User name cannot be null or empty.");
        }

        int delimiter = username.IndexOf("\\");
        if (delimiter > -1)
        {
            return username.Substring(0, delimiter);
        }
        else
        {
            return null;
        }
    }
}

Я подключаю это в своем startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    // configure DI for application services
    services.AddScoped<IBasicAuthenticationService, BasicAuthenticationActiveDirectoryService>();

    //Set up basic authentication
    services.AddAuthentication("BasicAuthentication")
        .AddScheme<Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);

    ...
}

Я настраиваю аутентификацию и авторизацию в файле startup.cs:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

   ...

    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseAuthorization();
    app.UseAuthentication();

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

О, хорошо, UseAuthentication заполняет пользователя (код, который выполняется до этого, не будет иметь действительного свойства HttpContext.User), UseAuthorization просматривает заполненного пользователя и текущую конечную точку, чтобы определить, нужно ли применять политику авторизации. Порядок, в котором компоненты промежуточного программного обеспечения добавляются в методе Startup.Configure, определяет порядок, в котором компоненты промежуточного программного обеспечения вызываются при запросах, и обратный порядок ответов. Порядок имеет решающее значение для безопасности, производительности и функциональности. Теперь это имеет смысл.

Jeremy 10.12.2020 19:09

Да, ты прав. ·UseAuthentication· проанализирует токен, а затем извлечет информацию о пользователе в HttpContext.User.

Karney. 11.12.2020 02:46
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
3 380
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Попробуйте поменять порядок app.UseAuthorization() и app.UseAuthentication(). Потому что UseAuthentication проанализирует токен, а затем извлечет информацию о пользователе в HttpContext.User. Затем UseAuthorization выполнит авторизацию на основе схемы аутентификации.

Другие вопросы по теме