Аутентификация носителя ASP.NET, не работает с swaggerм. Работает с почтальоном

Я пытаюсь настроить аутентификацию для своего веб-API ASP.NET. Я добавил аутентификацию jwt, используя Firebase Google в качестве поставщика удостоверений. Когда я пытаюсь пройти аутентификацию через пользовательский интерфейс Swagger, я могу сгенерировать свой токен JWT, я могу добавить токен с помощью кнопки «Авторизовать» в пользовательском интерфейсе Swagger, но когда я пытаюсь вызвать конечную точку, требующую аутентификации, я получаю 401. Когда я делаю тот же запрос от почтальона, я получаю 200, я просто не понимаю, почему!

Моя настройка в program.cs:

Настройка Swagger Gen:

...
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo { Title = "WorkoutBuddy", Version = "v1" });
    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        In = ParameterLocation.Header,
        Name = "Authorization",
        Description = "Provide JWT token",
        Type = SecuritySchemeType.Http,
        BearerFormat = "JWT",
        Scheme = "bearer"
    });
    options.AddSecurityRequirement(new OpenApiSecurityRequirement{
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type=ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            Array.Empty<string>()
        }
    });
});
...

Настройка JWT:

...
builder.Services
    .AddAuthentication()
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, jwtOptions =>
    {
        jwtOptions.Authority = builder.Configuration["Auth:ValidIssuer"];
        jwtOptions.Audience = builder.Configuration["Auth:Audience"];
        jwtOptions.TokenValidationParameters.ValidIssuer = builder.Configuration["Auth:ValidIssuer"];
    });
...

Настройка пользовательского интерфейса Swagger

...
    app.UseExceptionHandler("/error")
        .UseSwagger()
        .UseSwaggerUI(c =>
        {
            c.SwaggerEndpoint($"/swagger/v1/swagger.json", "WorkoutBuddy Api");
        });

    app.UseCors(options => options.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());

    app.UseHttpsRedirection();

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

    app.MapControllers();

    app.Run();
...

Запрос Swagger UI - 401

Запрос почтальона - 200

Попробовал настроить swagger ui всеми возможными способами, которые смог найти. Кажется, ничего не работает

Обновлять

Вот мой полный файл Program.cs

global using WorkoutBuddy.Data;
using Microsoft.EntityFrameworkCore;
using Azure.Identity;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using FirebaseAdmin;
using Google.Apis.Auth.OAuth2;
using WorkoutBuddy.Services;
using WorkoutBuddy.Features;
using Microsoft.OpenApi.Models;
using Microsoft.IdentityModel.Tokens;
using Swashbuckle.AspNetCore.Filters;

var builder = WebApplication.CreateBuilder(args);

var env = builder.Environment;

builder.Configuration
       .SetBasePath(env.ContentRootPath)
       .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
       .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
       .AddEnvironmentVariables()
       .AddUserSecrets<Program>();

// var azureCredentials = new DefaultAzureCredential(new DefaultAzureCredentialOptions
// {
//     ExcludeEnvironmentCredential = false,
//     ExcludeManagedIdentityCredential = false,
//     ExcludeSharedTokenCacheCredential = false,
//     ExcludeVisualStudioCredential = true,
//     ExcludeVisualStudioCodeCredential = true,
//     ExcludeAzureCliCredential = false,
//     ExcludeAzurePowerShellCredential = true,
//     ExcludeInteractiveBrowserCredential = true,
// });

// // Inject keyvault secrets
// var shouldUseKeyVault = builder.Configuration.GetValue<bool>("KeyVault:UseKeyVault", true);
// if (shouldUseKeyVault)
// {
//     var keyVaultUrl = builder.Configuration.GetValue<Uri>("KeyVault:Url"); // ?? throw new ArgumentNullException("Missing configuration: KeyVault:Url");

//     builder.Configuration.AddAzureKeyVault(keyVaultUrl, azureCredentials);
// }

// Setup EF Core to SQL db connection
builder.Services.AddDbContext<DataContext>(options =>
{
    var sqlDbConnectionString = builder.Configuration.GetConnectionString("SQL") ?? throw new ArgumentNullException("Missing SQL connectionstring");
    options.UseSqlServer(sqlDbConnectionString, sqlServerOptions =>
    {
        sqlServerOptions.CommandTimeout(30);
    });
});

// setup swagger options
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
// https://thewikihow.com/video_z46lqVOv1hQ&ab_channel=ThumbIKR-ProgrammingExamples
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo { Title = "WorkoutBuddy", Version = "v1" });
    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        In = ParameterLocation.Header,
        Name = "Authorization",
        Description = "Provide JWT token",
        Type = SecuritySchemeType.Http,
        BearerFormat = "JWT",
        Scheme = "bearer"
    });
    // options.AddSecurityRequirement(new OpenApiSecurityRequirement{
    //     {
    //         new OpenApiSecurityScheme
    //         {
    //             Reference = new OpenApiReference
    //             {
    //                 Type=ReferenceType.SecurityScheme,
    //                 Id = "Bearer"
    //             }
    //         },
    //         Array.Empty<string>()
    //     }
    // });
    options.OperationFilter<SecurityRequirementsOperationFilter>();
});

// setup application insight logging
// if (env.EnvironmentName != "Local")
// {
//     builder.Services.AddApplicationInsightsTelemetry(options =>
//     {
//         options.DeveloperMode = false;
//         options.ConnectionString = builder.Configuration.GetValue<string>("APPLICATIONINSIGHTS_CONNECTION_STRING") ?? throw new ArgumentNullException("Missing: APPLICATIONINSIGHTS_CONNECTION_STRING");
//     });
// }

// setup firebase authentication
// Convert firebase config json to string: https://tools.knowledgewalls.com/json-to-string
var firebaseConfig = builder.Configuration.GetValue<string>("Auth:FirebaseConfig"); // ?? throw new ArgumentNullException("missing firebase config");
FirebaseApp.Create(new AppOptions()
{
    Credential = GoogleCredential.FromJson(firebaseConfig)
});

builder.Services
    .AddAuthentication()
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, jwtOptions =>
    {
        jwtOptions.Authority = builder.Configuration["Auth:ValidIssuer"];
        jwtOptions.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration.GetValue<string>("Auth:ValidIssuer"),
            ValidAudience = builder.Configuration.GetValue<string>("Auth:Audience")
        };
        jwtOptions.Events = new JwtBearerEvents
        {
            OnAuthenticationFailed = context =>
            {
                Console.WriteLine("Unauthorized request: \n" + context.Exception);
                return Task.CompletedTask;
            }
        };
    });

// setup services
builder.Services.AddHttpContextAccessor();
// builder.Services.AddOutputCache(); // can be faulty if multiple instances

builder.Services.AddHttpClient<GoogleJwtProvider, GoogleJwtProvider>(httpClient =>
{
    var baseAddress = builder.Configuration["Auth:TokenUri"] ?? throw new ArgumentNullException("Missing: Auth:TokenUri");
    var apiKey = builder.Configuration["Auth:FirebaseApiKey"] ?? throw new ArgumentException("Missing firebase api key");
    httpClient.BaseAddress = new Uri($"{baseAddress}?key = {apiKey}");
});
builder.Services.AddScoped<UserService, UserService>();
builder.Services.AddScoped<ProfileService, ProfileService>();
builder.Services.AddScoped<WorkoutDetailService, WorkoutDetailService>();
builder.Services.AddScoped<ExerciseDetailService, ExerciseDetailService>();
builder.Services.AddScoped<WorkoutService, WorkoutService>();

var app = builder.Build();

switch (args.FirstOrDefault())
{
    case "initAndSeedDb":
        await app.InitAndSeedDb();
        return;
    case null:
        RunApp(app);
        return;
    case var arg: throw new ArgumentException($"Unknown command-line argument: {arg}");
}

static void RunApp(WebApplication app)
{
    // app.UseExceptionHandler("/error")
    //     .UseSwagger()
    //     .UseSwaggerUI(c =>
    //     {
    //         c.SwaggerEndpoint($"/swagger/v1/swagger.json", "WorkoutBuddy Api");
    //     });

    app.UseSwagger()
    .UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint($"/swagger/v1/swagger.json", "WorkoutBuddy Api");
    });

    app.UseCors(options => options.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());

    app.UseHttpsRedirection();
    // app.UseOutputCache(); // can be faulty if multiple instances of app running

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

    app.MapControllers();

    app.Run();
}

Вот контроллер, который я пытаюсь вызвать:

[Authorize]
[ApiController]
[Route("api/workout")]
public class WorkoutController : CustomControllerBase
{
    private readonly ILogger<WorkoutController> _logger;
    private readonly WorkoutService _workoutService;

    public WorkoutController(
        ILogger<WorkoutController> logger,
        WorkoutService workoutService)
    {
        _logger = logger;
        _workoutService = workoutService;
    }

    [HttpGet()]
    public async Task<ActionResult<IEnumerable<WorkoutResponse>>> GetAllWorkoutsByProfile()
    {
        var workoutsResult = await _workoutService.GetWorkoutsForProfile();

        return GetDataOrError(
            result: workoutsResult,
            resolveResponse: (w) => w.Select(e => new WorkoutResponse(e))
        );
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<WorkoutResponse>> GetWorkoutById([FromRoute][Required] Guid workoutId)
    {
        var workoutResult = await _workoutService.GetWorkoutById(workoutId);

        return GetDataOrError(
            result: workoutResult,
            resolveResponse: (w) => new WorkoutResponse(w)
        );
    }

    // delete workout by id 
    [HttpDelete("{id}")]
    public async Task<ActionResult<WorkoutResponse>> DeleteWorkout([FromRoute][Required] Guid workoutId)
    {
        var workoutResult = await _workoutService.DeleteWorkout(workoutId);

        return GetDataOrError(
            result: workoutResult,
            resolveResponse: (w) => new WorkoutResponse(w)
        );
    }

    // create workoutset by workout id

    // create exerciseset by workoutset id

    // update exerciseset by workoutset id
}

Я также заметил, что в текущей конфигурации блокировка выглядит странно после аутентификации в пользовательском интерфейсе Swagger:

Пользовательский интерфейс Swagger после аутентификации

Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
228
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я изменил параметры в вашем методе AddJwtBearer, он работает в моем локальном методе.

Вот пример кода

Программа.cs

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using System.Text;

namespace WebApplication1
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.
            builder.Services.AddControllers();
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc("v1", new OpenApiInfo { Title = "WorkoutBuddy", Version = "v1" });
                options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                {
                    In = ParameterLocation.Header,
                    Name = "Authorization",
                    Description = "Provide JWT token",
                    Type = SecuritySchemeType.Http,
                    BearerFormat = "JWT",
                    Scheme = "bearer"
                });
                options.AddSecurityRequirement(new OpenApiSecurityRequirement
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference
                            {
                                Type = ReferenceType.SecurityScheme,
                                Id = "Bearer"
                            },
                        },
                        Array.Empty<string>()
                    }
                });
            });

            builder.Services
                .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = true,
                        ValidateAudience = true,
                        ValidateIssuerSigningKey = true,
                        ValidIssuer = "Auth:ValidIssuer",
                        ValidAudience = "Auth:Audience",
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("this is my custom Secret key for authentication"))
                    };
                });

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            app.UseSwagger();
            app.UseSwaggerUI();

            app.UseHttpsRedirection();
            app.UseCors(options => options.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());

            app.UseAuthentication();  

            app.UseAuthorization();

            app.MapControllers();

            app.Run();
        }
    }
}

Мой тестовый контроллер

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace WebApplication1.Controllers
{
    [Authorize]
    [ApiController]
    [Route("[controller]")]
    public class TestController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<TestController> _logger;

        public TestController(ILogger<TestController> logger)
        {
            _logger = logger;
            
        }
        [AllowAnonymous]
        [HttpPost("login")]
        public IActionResult Login()
        {

            var token = GenerateJwtToken("Jason");

            return Ok(new { Token = token });
        }

        private string GenerateJwtToken(string username)
        {
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("this is my custom Secret key for authentication"));
            var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

            var claims = new[]
            {
            new Claim(JwtRegisteredClaimNames.Sub, username),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
        };

            var token = new JwtSecurityToken(
                issuer: "Auth:ValidIssuer",
                audience: "Auth:Audience",
                claims: claims,
                expires: DateTime.Now.AddMinutes(30),
                signingCredentials: credentials
            );

            return new JwtSecurityTokenHandler().WriteToken(token);
        }
        [HttpGet("test")]
        public IActionResult TestMethod1()
        {
            return Ok("test is ok");
        }
    }
}

Результат испытаний

Благодарю за ваш ответ! К сожалению, у меня это до сих пор не работает. Я не знаю, вызывает ли у меня эту проблему использование аутентификации Firebase. А пока я остановлюсь на почтальоне...

Frederik 18.04.2024 21:03

Привет @Frederik Не могли бы вы создать минимальный образец, способный воспроизвести для меня проблему? И, пожалуйста, не забудьте скрыть конфиденциальную информацию, я рассмотрю проблему позже, большое спасибо.

Jason Pan 18.04.2024 23:41

Привет, Джейсон, я пытался создать минимальный образец, но по какой-то причине он работает так, как задумано...

Frederik 20.04.2024 10:41

Я обновил свой вопрос, добавив дополнительную информацию. Может быть, вы видите то, чего я не вижу?

Frederik 20.04.2024 15:50
Ответ принят как подходящий

После создания нового проекта с нуля, повторного использования всех моих настроек аутентификации и развязности из исходного проекта я наконец нашел причину. Когда я получаю доступ к своему пользовательскому интерфейсу Swagger с помощью http: http://localhost:5173/swagger/index.html, мой Swagger не проходит аутентификацию. Почтальон отлично работает при аутентификации на моем API с помощью http. Но когда я перехожу на использование swagger ui с https: https://localhost:5272/swagger/index.html аутентификация с помощью swagger ui работает как положено.

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