Ошибка токена JWT BearerHandler, но запрос все еще обрабатывается

Я столкнулся с очень странной проблемой, и я предполагаю, что мне что-то не хватает в моей настройке.

У меня есть WebAPI, защищенный IdentityServer4. Он использует только Client_credentials. Если я напишу неправильный ClientId или ClientSecret, этот пользователь не будет аутентифицирован, и я не смогу подключиться к своему WebAPI. Но если я пишу неправильное имя области, запрос все еще обрабатывается, и я получаю ответ, странная часть заключается в том, что возникает исключение, но по какой-то причине оно игнорируется .NET Core Framework.

Вот некоторая отладочная информация из моего окна вывода.

Microsoft.AspNetCore.Hosting.Internal.WebHost:Информация: запрос запуска HTTP/1.1 GET https://localhost:44360/v1/bookings

    Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:Information: Failed to validate the token.
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidAudienceException: IDX10214: Audience validation failed. Audiences: '[PII is hidden]'. Did not match: validationParameters.ValidAudience: '[PII is hidden]' or validationParameters.ValidAudiences: '[PII is hidden]'.
           at Microsoft.IdentityModel.Tokens.Validators.ValidateAudience(IEnumerable`1 audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
           at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateAudience(IEnumerable`1 audiences, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
           at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateTokenPayload(JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
           at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
           at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
        Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:Information: Bearer was not authenticated. Failure message: IDX10214: Audience validation failed. Audiences: '[PII is hidden]'. Did not match: validationParameters.ValidAudience: '[PII is hidden]' or validationParameters.ValidAudiences: '[PII is hidden]'.
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executing endpoint 'TRS.BookingService.Api.Controllers.BookingsController.Get (TRS.BookingService.Api)'
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Route matched with {action = "Get", controller = "Bookings"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.ActionResult`1[System.Collections.Generic.IEnumerable`1[System.String]]] Get() on controller TRS.BookingService.Api.Controllers.BookingsController (TRS.BookingService.Api).
        Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executing action method TRS.BookingService.Api.Controllers.BookingsController.Get (TRS.BookingService.Api) - Validation state: Valid
        TRS.BookingService.Api.Controllers.BookingsController:Information: Getting all bookings
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action method TRS.BookingService.Api.Controllers.BookingsController.Get (TRS.BookingService.Api), returned result Microsoft.AspNetCore.Mvc.ObjectResult in 96.2159ms.
Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor:Information: Executing ObjectResult, writing value of type 'System.String[]'.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action TRS.BookingService.Api.Controllers.BookingsController.Get (TRS.BookingService.Api) in 280.2344ms
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executed endpoint 'TRS.BookingService.Api.Controllers.BookingsController.Get (TRS.BookingService.Api)'
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 1345.3829ms 200 application/json; charset=utf-8

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

Вот как выглядит ConfigureServices:

    services
        .AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(options =>
        {
            options.Authority = "https://localhost:44392/";
            options.Audience = "FAKE_SCOPE";
        });

И методы Configure()

    app.UseAuthentication();
    app.UseMvc();

Вот как выглядит токен JWT:

{
  "nbf": 1562062882,
  "exp": 1562066482,
  "iss": "https://localhost:44392",
  "aud": [
    "https://localhost:44392/resources",
    "bookingApi"
  ],
  "client_id": "clientId",
  "scope": [
    "bookingApi"
  ]
}

А это клиентский код, вызывающий API.

        var idpUrl = "https://localhost:44392/";    
        var clientId = "clientId";
        var clientSecret = "secret";
        var scope = "bookingApi";

        var accessToken = await GetAccessTokenAsync(new Uri(idpUrl), clientId, clientSecret, scope);
        string content = await GetContent(new Uri("https://localhost:44360/v1/bookings"), accessToken);

Я думаю, что я что-то пропустил, когда дело доходит до авторизации, я пробовал разные

services.Authorization()

В методах ConfigureServices(), но это не помогает, думаю, я написал это неправильно.

С наилучшими пожеланиями Магнус Полезно Бесполезно


person Magnus Gladh    schedule 02.07.2019    source источник
comment
Вы добавили атрибут Authorize над своим BookingsController.Get()?   -  person d_f    schedule 02.07.2019
comment
Да, иначе никакой авторизации не было.   -  person Magnus Gladh    schedule 03.07.2019
comment
Я вижу твой ответ. это не должно быть ожидаемым поведением. фильтр (т.е.) атрибут должен прерывать поток. и я почти уверен, что это сработало для меня, как и ожидалось. перепроверит. какую версию ядра asp.net вы используете? все ведут себя немного по разному   -  person d_f    schedule 03.07.2019
comment
Да, я знаю, это не должно быть ожидаемым поведением. Я использую версию 2.2.   -  person Magnus Gladh    schedule 03.07.2019
comment
созданное решение API из шаблона по умолчанию. запущено как есть: получено 200 с localhost:57358/api/values. добавлено: services.AddAuthentication(options => {options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;}).AddJwtBearer(options =>{options.Authority = "https://login.dev.xxx.com/"; //options.Audience = "FAKE_SCOPE";}); + Authorize на контроллере Values -- получилось 401 + WWW-Authenticate: Bearer error="invalid_token", error_description="The audience is invalid".   -  person d_f    schedule 03.07.2019
comment
возможно у вас прописана еще какая-то схема аутентификации, т.е. cookie, и она работает как фолбэк. в этом случае вам нужно явно указать единственную разрешенную схему, т.е. [Authorize (AuthenticationSchemes = "Bearer" )]   -  person d_f    schedule 03.07.2019
comment
Это может быть правдой, нигде не могу найти. Но, несмотря на это, единственное изменение состоит в том, что я делаю дополнительную проверку, чтобы быть действительно уверенным, что вообще нет никаких сбоев. Поэтому я думаю, что пока оставлю свое собственное промежуточное программное обеспечение. Одна проблема, которая может возникнуть, заключается в том, что если Microsoft внесет какие-либо изменения в свой класс AuthenticationMiddleware, я никогда не получу их, даже если обновлю пакеты.   -  person Magnus Gladh    schedule 08.07.2019
comment
Вы правы относительно возможных изменений в ядре. А я предложил стандартную подсказку, т.е. [Authorize (AuthenticationSchemes = "Bearer" )]. Решение за вами : )   -  person d_f    schedule 08.07.2019


Ответы (2)


Я наткнулся на эту статью после того, как столкнулся с той же проблемой. После долгих ударов головой я обнаружил, что в моем случае это было вызвано использованием services.AddMvcCore() (с .AddJsonFormatters().AddDataAnnotations() в моем случае), а не services.AddMvc(). Только с .AddMvcCore() я получаю 401 для ошибки проверки токена.

Кажется, вам нужно добавить .AddAuthorization() в микс при использовании .AddMvcCore, поскольку он не добавляется по умолчанию. Без него проверка токена завершается ошибкой, но конвейер запросов продолжает успешно работать.

person fubaar    schedule 23.09.2019

Проведя день, пытаясь понять, почему он не работает, я решил выполнить код Microsoft и нашел его в AuthenticationMiddleware.

открытый класс AuthenticationMiddleware { частный только для чтения RequestDelegate _next;

public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes)
{
    if (next == null)
    {
        throw new ArgumentNullException(nameof(next));
    }
    if (schemes == null)
    {
        throw new ArgumentNullException(nameof(schemes));
    }

    _next = next;
    Schemes = schemes;
}

public IAuthenticationSchemeProvider Schemes { get; set; }

public async Task Invoke(HttpContext context)
{
    context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
    {
        OriginalPath = context.Request.Path,
        OriginalPathBase = context.Request.PathBase
    });

    // Give any IAuthenticationRequestHandler schemes a chance to handle the request
    var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
    foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
    {
        var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;
        if (handler != null && await handler.HandleRequestAsync())
        {
            return;
        }
    }

    var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
    if (defaultAuthenticate != null)
    {
        var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
        if (result?.Principal != null)
        {
            context.User = result.Principal;
        }
    }

    await _next(context);
}

}

И в основном происходит то, что результат имеет свойства Failure, которые содержат мое исключение аутентификации, но, поскольку в коде нет проверки для этого, он продолжит запрос к следующему промежуточному программному обеспечению в конвейере. Поэтому я в основном написал свой собственный AuthenticationMiddleware, добавив проверку, имеет ли значение сбой, а затем возвращает 403.

        var defaultAuthenticate = await _schemas.GetDefaultAuthenticateSchemeAsync();
        if (defaultAuthenticate != null)
        {
            var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
            if (result?.Principal != null)
                context.User = result.Principal;

            if (result?.Failure != null)
                throw new AuthorizationException(result.Failure.Message);
        }

        await _next(context);
    }
    catch (AuthorizationException ex) when (!context.Response.HasStarted)
    {
        _logger.LogWarning(ex, "Unauthorized access encountered.");

        context.Response.Clear();
        context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
    }

Однако это не то, что мог бы сделать только я, поэтому, если кто-то знает, зачем мне это нужно, я был бы рад получить информацию.

person Magnus Gladh    schedule 03.07.2019