Пользовательские модели ответов 401 и 403 с промежуточным программным обеспечением UseJwtBearerAuthentication

Я хочу ответить моделью ответа JSON, когда возникают 401 и 403. Например:

HTTP 401
{
  "message": "Authentication failed. The request must include a valid and non-expired bearer token in the Authorization header."
}

Я использую промежуточное программное обеспечение (как предложено вэтот ответ) для перехвата 404-х, и он прекрасно работает, но это не относится к 401 или 403-м. Вот промежуточное ПО:

app.Use(async (context, next) =>
{
    await next();
    if (context.Response.StatusCode == 401)
    {
        context.Response.ContentType = "application/json";
        await context.Response.WriteAsync(JsonConvert.SerializeObject(UnauthorizedModel.Create(), SerializerSettings), Encoding.UTF8);
    }
});

Когда помещены НИЖЕapp.UseJwtBearerAuthentication(..) вStartup.Configure(..), кажется, полностью игнорируется и возвращается нормальный 401.

Когда размещены вышеapp.UseJwtBearerAuthentication(..) вStartup.Configure(..)затем выдается следующее исключение:

Идентификатор соединения "0HKT7SUBPLHEM": приложение сгенерировало необработанное исключение. System.InvalidOperationException: Заголовки только для чтения, ответ уже начался. в Microsoft.AspNetCore.Server.Kestrel.Internal.Http.FrameHeaders.Microsoft.AspNetCore.Http.IHeaderDictionary.set_Item (строковый ключ, значение StringValues) в Microsoft.AspNetCore.Http.Internal.DefaultHttpResponT.Project.ject Api.Startup. <B__12_0> d.MoveNext () в Startup.cs

Ответы на вопрос(2)

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

Вот пример, который вернет ответ 401, содержащий код ошибки / описание OAuth2 в виде простого текста (вы, конечно, можете вернуть JSON или что угодно):

app.UseJwtBearerAuthentication(new JwtBearerOptions
{
    Authority = "http://localhost:54540/",
    Audience = "http://localhost:54540/",
    RequireHttpsMetadata = false,
    Events = new JwtBearerEvents
    {
        OnChallenge = async context =>
        {
            // Override the response status code.
            context.Response.StatusCode = 401;

            // Emit the WWW-Authenticate header.
            context.Response.Headers.Append(
                HeaderNames.WWWAuthenticate,
                context.Options.Challenge);

            if (!string.IsNullOrEmpty(context.Error))
            {
                await context.Response.WriteAsync(context.Error);
            }

            if (!string.IsNullOrEmpty(context.ErrorDescription))
            {
                await context.Response.WriteAsync(context.ErrorDescription);
            }

            context.HandleResponse();
        }
    }
});

Кроме того, вы также можете использовать промежуточное программное обеспечение кодовых страниц состояния, но для 403 ответов у вас не будет никаких намеков на политику авторизации, которая вызвала это:

app.UseStatusCodePages(async context =>
{
    if (context.HttpContext.Request.Path.StartsWithSegments("/api") &&
       (context.HttpContext.Response.StatusCode == 401 ||
        context.HttpContext.Response.StatusCode == 403))
    {
        await context.HttpContext.Response.WriteAsync("Unauthorized request");
    }
});
 Dave New10 июл. 2016 г., 08:03
Какая жалость. Есть ли веская причина, почемуapp.UseStatusCodePages() не будет использоваться для 403-х годов?
 Pinpoint09 июл. 2016 г., 16:37
К сожалению, применение той же логики к 403 ответам будет намного сложнее (потому чтоChallenge событие не вызывается в этом случае). Одним из вариантов может быть создание подкласса обработчика переноса JWT в соответствии с предложением @Set и переопределениеHandleForbiddenAsync поступать правильно. Надеюсь, это будет улучшено в следующей версии:github.com/aspnet/Security/issues/872.
 Pedro Moreira26 авг. 2016 г., 17:41
По-видимомуIApplicationBuilder не имеетUseStatusCodePages() метод!
 Pedro Moreira26 авг. 2016 г., 17:07
Можно ли просто вернутьIActionResult вместо?
 Pinpoint26 авг. 2016 г., 17:22
app.UseStatusCodePages() имеет перегрузку, позволяющую повторить запрос с другим путем. Так что да, вы можете обработать это в действии контроллера и вернутьIActionResult.
 Dave New09 июл. 2016 г., 16:27
Ах, это хорошо работает для аутентификации! Хотя ни одно из этих событий, по-видимому, не вызывает сбой авторизации (403). Есть идеи здесь?
 Pinpoint16 июл. 2016 г., 19:58
@davenewza К сожалению, по некоторым причинам, я пропустил ваш вопрос, извините. На самом деле, вы можете использовать промежуточное программное обеспечение кодовых страниц состояния (я обновил мой ответ), но у вас нет возможности определить причину ответа 403, поэтому вы можете только вернуть очень общее сообщение об ошибке, которое, вероятно, не очень поможет пользователи вашего API.
 Pedro Moreira26 авг. 2016 г., 18:35
В итоге я использовал ваш первый подход и настроил объект Response вручную. Благодарю.

промежуточное программное это важно.

Каждое промежуточное программное обеспечение выбирает, передавать ли запрос следующему компоненту в конвейере, и может выполнять определенные действия до и после вызова следующего компонента в конвейере.

UseJwtBearerAuthentication останавливает дальнейшее выполнение конвейера, если произошла ошибка.

Но ваш подход не работает с промежуточным программным обеспечением JwtBearerAuthentication, так как при возникновении неавторизованной ошибки промежуточное программное обеспечение отправляет заголовок WWWAuthenticate, поэтому вы получаете исключение «ответ уже запущен» - посмотритеHandleUnauthorizedAsync метод. Вы можете переопределить этот метод и реализовать свою собственную пользовательскую логику.

Другое возможное решение (не уверен, что работает) заключается в использованииHttpContext.Response.OnStarting обратный вызов в вашем промежуточном программном обеспечении, как он вызывается перед отправкой заголовка. Вы можете посмотреть на этоТак ответь

 Dave New09 июл. 2016 г., 13:08
«System.InvalidOperationException: заголовки доступны только для чтения, ответ уже запущен», если он помещен перед UseJwtBearerAuthentication, генерируется.
 Set09 июл. 2016 г., 13:46
надеюсь, обновление поможет

Ваш ответ на вопрос