Пользовательское промежуточное ПО для проверки подлинности: InvalidOperationException при неудачной проверке подлинности

Я хочу аутентифицировать вызовы моего приложения ASP.NET Core 1.0, используя заголовок HTTP, например Authorization ToggledKey 10079a4c-d27e-4898-915a-968850c756ef

Идея в том, что я могу выдавать ключи API, и люди могут их использовать, отзывать их и т. д. Мне нужен очень простой ключ — я не хочу влезать в OAuth, IdentityServer или что-то еще.

Я следил за этим руководством, в котором рассказывается о том, как «подделать» успешный ответ для целей тестирования, и в итоге я получил следующий код:

public class TestAuthenticationOptions : AuthenticationOptions  
{
    public virtual ClaimsIdentity Identity { get; } = new ClaimsIdentity(new Claim[]
    {
        new Claim(ClaimTypes.Name, Guid.NewGuid().ToString()),
        // Other claims omitted for brevity
    }, "Toggled");

    public TestAuthenticationOptions()
    {
        this.AuthenticationScheme = "ToggledAuthenticationMiddleware";
        this.AutomaticAuthenticate = true;
    }
}

public class TestAuthenticationHandler : AuthenticationHandler<TestAuthenticationOptions>  
{
    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var authenticationTicket = new AuthenticationTicket(
                                        new ClaimsPrincipal(Options.Identity),
                                        new AuthenticationProperties(),
                                        this.Options.AuthenticationScheme);

        return Task.FromResult(AuthenticateResult.Success(authenticationTicket));
    }
}

public class TestAuthenticationMiddleware : AuthenticationMiddleware<TestAuthenticationOptions>  
{
    private readonly RequestDelegate next;

    public TestAuthenticationMiddleware(RequestDelegate next, IOptions<TestAuthenticationOptions> options, ILoggerFactory loggerFactory)
        : base(next, options, loggerFactory, System.Text.Encodings.Web.UrlEncoder.Default)
    {
        this.next = next;
    }

    protected override AuthenticationHandler<TestAuthenticationOptions> CreateHandler()
    {
        return new TestAuthenticationHandler();
    }
}

Для начала я просто пытаюсь получить жестко закодированную успешную аутентификацию, которая позволит мне увидеть пользователя с User.Identity.Name, и жестко закодированную неудачную аутентификацию, чтобы увидеть 401 Unauthorized — просто чтобы понять, как это работает.

Добавлено в метод Configure:

app.UseMiddleware<TestAuthenticationMiddleware>(); 
app.UseMvc();

Пока все хорошо, все работает - атрибут [Authorize] передается на контроллер, и я получаю GUID в User.Identity.Name

Проблема:

Когда я меняю для неудачного ответа:

return Task.FromResult(AuthenticateResult.Fail("Auth Failed!"));
//return Task.FromResult(AuthenticateResult.Success(authenticationTicket));

Вместо 401 я получаю 500 со следующим исключением:

Unknown error responding to request: InvalidOperationException:
System.InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic
at Microsoft.AspNetCore.Http.Authentication.Internal.DefaultAuthenticationManager.<ChallengeAsync>d__12.MoveNext()

Я вижу ошибку аутентификации в промежуточном программном обеспечении:

[Information] ToggledAppServices.TestAuthenticationMiddleware: ToggledAuthenticationMiddleware was not authenticated. Failure message: Auth failed! 

Итак, я вижу, что он использовал промежуточное программное обеспечение, не прошел аутентификацию, но затем также вижу непосредственно перед InvalidOperationException:

[Information] Microsoft.AspNetCore.Mvc.ChallengeResult: Executing ChallengeResult with authentication schemes ().

После этого, хотя я довольно смущен. Что я могу сделать, чтобы сделать мое промежуточное ПО «единственной» точкой аутентификации, и в случае сбоя возвращать 401 вместо того, чтобы пытаться сделать что-то еще и потерпеть неудачу с InvalidOperationException?

Полные журналы:

[Debug] Microsoft.AspNetCore.Hosting.Internal.WebHost: Hosting starting 
[Debug] Microsoft.AspNetCore.Hosting.Internal.WebHost: Hosting started 
START RequestId: 52c7358f-e50a-11e7-a5a6-b3632cca0b75 Version: $LATEST
Incoming GET requests to /api/values
[Information] Microsoft.AspNetCore.Hosting.Internal.WebHost: Request starting GET https://www.example.com/api/controller application/json 
[Information] ToggledAppServices.TestAuthenticationMiddleware: ToggledAuthenticationMiddleware was not authenticated. Failure message: Auth failed! 
[Debug] Microsoft.AspNetCore.Routing.Tree.TreeRouter: Request successfully matched the route with name '' and template 'api/values'. 
[Debug] Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker: Executing action ToggledAppServices.Controllers.ValuesController.Get (ToggledAppServices) 
[Information] Microsoft.AspNetCore.Authorization.DefaultAuthorizationService: Authorization failed for user: . 
[Warning] Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'. 
[Information] Microsoft.AspNetCore.Mvc.ChallengeResult: Executing ChallengeResult with authentication schemes (). 
Unknown error responding to request: InvalidOperationException:
System.InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic
at Microsoft.AspNetCore.Http.Authentication.Internal.DefaultAuthenticationManager.<ChallengeAsync>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.ChallengeResult.<ExecuteResultAsync>d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeResultAsync>d__32.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeAsync>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Builder.RouterMiddleware.<Invoke>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware`1.<Invoke>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware`1.<Invoke>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Hosting.Internal.RequestServicesContainerMiddleware.<Invoke>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction.<ProcessRequest>d__15.MoveNext()
InvalidOperationException:
System.InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic
at Microsoft.AspNetCore.Http.Authentication.Internal.DefaultAuthenticationManager.<ChallengeAsync>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.ChallengeResult.<ExecuteResultAsync>d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeResultAsync>d__32.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeAsync>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Builder.RouterMiddleware.<Invoke>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware`1.<Invoke>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware`1.<Invoke>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Hosting.Internal.RequestServicesContainerMiddleware.<Invoke>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction.<ProcessRequest>d__15.MoveNext()

[Information] Microsoft.AspNetCore.Hosting.Internal.WebHost: Request finished in 7360.5673ms 0 
Response Base 64 Encoded: False
END RequestId: 52c7358f-e50a-11e7-a5a6-b3632cca0b75

person bgs264    schedule 20.12.2017    source источник
comment
Вы используете ASP.NET Core 1.x, верно? Есть ли причина, по которой вы не можете использовать 2.0? Стек аутентификации стал намного проще настраивать в версии 2.0.   -  person poke    schedule 20.12.2017
comment
@poke Он работает в AWS Lambda, поэтому в настоящее время это единственная поддерживаемая версия. Я с нетерпением жду, когда они обеспечат поддержку 2 — я знаю, что это было объявлено, но еще не готово к использованию.   -  person bgs264    schedule 20.12.2017


Ответы (1)


System.InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic
  at Microsoft.AspNetCore.Http.Authentication.Internal.DefaultAuthenticationManager.<ChallengeAsync>d__12.MoveNext()

Это говорит о том, что аутентификация вашего обработчика аутентификации работает должным образом и в результате неудачной аутентификации правильно создается результат запроса.

Итак, теперь обработчик проверки подлинности вызова по умолчанию должен запускаться для обработки запроса вызова. Однако схема вызова по умолчанию не настроена, поэтому это не удается.

Чтобы сообщить вашему TestAuthenticationHandler, что он также должен обрабатывать запросы на вызов, вы должны установить для параметра AutomaticChallenge вашего TestAuthenticationOptions типа значение true:

// handle authenticate requests by default
AutomaticAuthenticate = true;

// handle challenge requests by default
AutomaticChallenge = true;

Затем ваш обработчик будет запускаться для запросов вызова, и, поскольку вы не предоставляете пользовательскую реализацию, поведение по умолчанию просто генерирует HTTP-ответ 401.

person poke    schedule 20.12.2017
comment
Работает как шарм! Спасибо - я сжег так много часов с этим :) - person bgs264; 20.12.2017
comment
Аутентификация может быть мучительной — надеюсь, у вас скоро появится возможность перейти на ASP.NET Core 2! :) - person poke; 20.12.2017