Вопрос или проблема
Я создал ASP.NET Core Web API, используя мастер XAF 24.1.6 с Entity Framework Core и стандартной аутентификацией. Когда я его запускаю, я вижу интерфейс Swagger. Когда я нажимаю кнопку “Авторизоваться”, я могу ввести любой ключ, и он покажет, что вход выполнен. Как мне заставить кнопку “Авторизоваться” валидировать ключ?
используя DevExpress.ExpressApp.Security;
используя DevExpress.Persistent.Base;
используя Microsoft.EntityFrameworkCore;
используя DevExpress.Persistent.BaseImpl.EF.PermissionPolicy;
используя System.Text;
используя Microsoft.AspNetCore.Authentication.JwtBearer;
используя Microsoft.AspNetCore.Authorization;
используя Microsoft.IdentityModel.Tokens;
используя Microsoft.OpenApi.Models;
используя DevExpress.ExpressApp.WebApi.Services;
используя Microsoft.AspNetCore.OData;
используя MyXafApi.WebApi.JWT;
используя DevExpress.ExpressApp.Security.Authentication.ClientServer;
используя DevExpress.ExpressApp;
используя DevExpress.ExpressApp.ApplicationBuilder;
namespace MyXafApi.WebApi;
public class Startup {
public Startup(IConfiguration configuration) {
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// Этот метод вызывается во время выполнения. Используйте этот метод для добавления услуг в контейнер.
// Для получения дополнительной информации о том, как настроить ваше приложение, посетите https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services) {
services.AddScoped<IAuthenticationTokenProvider, JwtTokenProviderService>();
services.AddXafWebApi(builder => {
builder.ConfigureOptions(options => {
// Сделайте ваши бизнес-объекты доступными в Web API и сгенерируйте методы HTTP GET, POST, PUT и DELETE для них.
// options.BusinessObject<YourBusinessObject>();
});
builder.Modules
.Add<MyXafApi.WebApi.MyXafApiModule>();
builder.ObjectSpaceProviders
.AddSecuredEFCore(options => options.PreFetchReferenceProperties())
.WithDbContext<MyXafApi.WebApi.BusinessObjects.MyXafApiEFCoreDbContext>((serviceProvider, options) => {
// Раскомментируйте этот код, чтобы использовать базу данных в памяти. Эта база данных создается заново каждый раз при запуске сервера. При использовании базы данных в памяти не нужно делать миграцию при изменении модели данных.
// Не используйте этот код в производственной среде, чтобы избежать потери данных.
// Мы рекомендуем вам ознакомиться со следующим разделом справки перед использованием базы данных в памяти: https://docs.microsoft.com/en-us/ef/core/testing/in-memory
//options.UseInMemoryDatabase("InMemory");
string connectionString = null;
if(Configuration.GetConnectionString("ConnectionString") != null) {
connectionString = Configuration.GetConnectionString("ConnectionString");
}
ArgumentNullException.ThrowIfNull(connectionString);
options.UseSqlServer(connectionString);
options.UseChangeTrackingProxies();
options.UseObjectSpaceLinkProxies();
options.UseLazyLoadingProxies();
})
.AddNonPersistent();
builder.Security
.UseIntegratedMode(options => {
options.Lockout.Enabled = true;
options.RoleType = typeof(PermissionPolicyRole);
// ApplicationUser наследуется от PermissionPolicyUser и поддерживает аутентификацию OAuth. Для получения дополнительной информации см. следующий раздел: https://docs.devexpress.com/eXpressAppFramework/402197
// Если ваше приложение использует PermissionPolicyUser или пользовательский тип, установите свойство UserType следующим образом:
options.UserType = typeof(MyXafApi.WebApi.BusinessObjects.ApplicationUser);
// ApplicationUserLoginInfo необходим только для приложений, которые используют тип пользователя ApplicationUser.
// Если вы используете PermissionPolicyUser или пользовательский тип, закомментируйте следующую строку:
options.UserLoginInfoType = typeof(MyXafApi.WebApi.BusinessObjects.ApplicationUserLoginInfo);
options.Events.OnSecurityStrategyCreated += securityStrategy => {
((SecurityStrategy)securityStrategy).PermissionsReloadMode = PermissionsReloadMode.CacheOnFirstAccess;
};
})
.AddPasswordAuthentication(options => {
options.IsSupportChangePassword = true;
});
builder.AddBuildStep(application => {
application.ApplicationName = "SetupApplication.MyXafApi";
application.CheckCompatibilityType = DevExpress.ExpressApp.CheckCompatibilityType.DatabaseSchema;
#if DEBUG
if(System.Diagnostics.Debugger.IsAttached && application.CheckCompatibilityType == CheckCompatibilityType.DatabaseSchema) {
application.DatabaseUpdateMode = DatabaseUpdateMode.UpdateDatabaseAlways;
application.DatabaseVersionMismatch += (s, e) => {
e.Updater.Update();
e.Handled = true;
};
}
#endif
});
}, Configuration);
services
.AddControllers()
.AddOData((options, serviceProvider) => {
options
.AddRouteComponents("api/odata", new EdmModelBuilder(serviceProvider).GetEdmModel())
.EnableQueryFeatures(100);
});
services.AddAuthentication()
.AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters() {
ValidateIssuerSigningKey = true,
//ValidIssuer = Configuration["Authentication:Jwt:Issuer"],
//ValidAudience = Configuration["Authentication:Jwt:Audience"],
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Authentication:Jwt:IssuerSigningKey"]))
};
});
services.AddAuthorization(options => {
options.DefaultPolicy = new AuthorizationPolicyBuilder(
JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.RequireXafAuthentication()
.Build();
});
services.AddSwaggerGen(c => {
c.EnableAnnotations();
c.SwaggerDoc("v1", new OpenApiInfo {
Title = "MyXafApi API",
Version = "v1",
Description = @"Используйте AddXafWebApi(options) в файле MyXafApi.WebApi\Startup.cs, чтобы сделать бизнес-объекты доступными в Web API."
});
c.AddSecurityDefinition("JWT", new OpenApiSecurityScheme() {
Type = SecuritySchemeType.Http,
Name = "Bearer",
Scheme = "bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement() {
{
new OpenApiSecurityScheme() {
Reference = new OpenApiReference() {
Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
Id = "JWT"
}
},
new string[0]
},
});
});
services.Configure<Microsoft.AspNetCore.Mvc.JsonOptions>(o => {
//Код ниже указывает, что именование свойств в объекте, сериализуемом в JSON, должно всегда точно соответствовать
//именам свойств соответствующего CLR типа, чтобы имена свойств отображались корректно в интерфейсе Swagger.
//XPO регистронезависим и требует этой настройки, чтобы пример данных запроса, отображаемый Swagger, всегда был действителен.
//Закомментируйте этот код, чтобы вернуться к поведению по умолчанию.
//См. следующую статью для получения дополнительной информации: https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializeroptions.propertynamingpolicy
o.JsonSerializerOptions.PropertyNamingPolicy = null;
});
}
// Этот метод вызывается во время выполнения. Используйте этот метод для настройки конвейера HTTP-запросов.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
if(env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => {
c.SwaggerEndpoint("/swagger/v1/swagger.json", "MyXafApi WebApi v1");
});
}
else {
app.UseExceptionHandler("/Error");
// Значение по умолчанию для HSTS составляет 30 дней. Чтобы изменить это для производственных сценариев, см.: https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRequestLocalization();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
endpoints.MapXafEndpoints();
});
}
}
и
используя System.Reflection;
используя DevExpress.ExpressApp;
используя DevExpress.ExpressApp.AspNetCore.DesignTime;
используя DevExpress.ExpressApp.Design;
используя DevExpress.ExpressApp.Utils;
namespace MyXafApi.WebApi;
public class Program : IDesignTimeApplicationFactory {
private static bool ContainsArgument(string[] args, string argument) {
return args.Any(arg => arg.TrimStart("https://stackoverflow.com/").TrimStart('-').ToLower() == argument.ToLower());
}
public static int Main(string[] args) {
if(ContainsArgument(args, "help") || ContainsArgument(args, "h")) {
Console.WriteLine("Обновляет базу данных, когда ее версия не соответствует версии приложения.");
Console.WriteLine();
Console.WriteLine($" {Assembly.GetExecutingAssembly().GetName().Name}.exe --updateDatabase [--forceUpdate --silent]");
Console.WriteLine();
Console.WriteLine("--forceUpdate - Указывает, что базу данных необходимо обновить, вне зависимости от того, соответствует ли ее версия версии приложения или нет.");
Console.WriteLine("--silent - Указывает, что обновление базы данных происходит автоматически и не требует взаимодействия с пользователем.");
Console.WriteLine();
Console.WriteLine($"Коды выхода: 0 - {DBUpdaterStatus.UpdateCompleted}");
Console.WriteLine($" 1 - {DBUpdaterStatus.UpdateError}");
Console.WriteLine($" 2 - {DBUpdaterStatus.UpdateNotNeeded}");
}
else {
DevExpress.ExpressApp.FrameworkSettings.DefaultSettingsCompatibilityMode = DevExpress.ExpressApp.FrameworkSettingsCompatibilityMode.Latest;
DevExpress.ExpressApp.Security.SecurityStrategy.AutoAssociationReferencePropertyMode = DevExpress.ExpressApp.Security.ReferenceWithoutAssociationPermissionsMode.AllMembers;
IHost host = CreateHostBuilder(args).Build();
if(ContainsArgument(args, "updateDatabase")) {
using(var serviceScope = host.Services.CreateScope()) {
return serviceScope.ServiceProvider.GetRequiredService<DevExpress.ExpressApp.Utils.IDBUpdater>().Update(ContainsArgument(args, "forceUpdate"), ContainsArgument(args, "silent"));
}
}
else {
host.Run();
}
}
return 0;
}
XafApplication IDesignTimeApplicationFactory.Create() {
IHostBuilder hostBuilder = CreateHostBuilder(Array.Empty<string>());
return DesignTimeApplicationFactoryHelper.Create(hostBuilder);
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => {
webBuilder.UseStartup<Startup>();
});
}
Ответ или решение
Проблема авторизации в Swagger: Ошибки с неверным ключом
Вы столкнулись с распространенной проблемой при разработке ASP.NET Core Web API, когда Swagger UI позволяет авторизовать пользователя с любым введенным ключом. Это может создать уязвимости в вашем приложении, поэтому важно правильно настроить процесс проверки ключа авторизации.
1. Понимание текущей конфигурации
Ваша конфигурация настроена на использование JSON Web Tokens (JWT) для аутентификации, что является хорошей практикой. Однако, как отметили, текущая реализация не проверяет действительность токена, что приводит к тому, что любой введенный ключ считается действительным.
В вашем коде, на уровне настройки аутентификации, стоит обратить внимание на параметры в TokenValidationParameters
. Вот пример вашего текущего конфигурационного кода:
services.AddAuthentication()
.AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters() {
ValidateIssuerSigningKey = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Authentication:Jwt:IssuerSigningKey"]))
};
});
2. Важные параметры проверки токена
Чтобы исправить ситуацию, вам нужно убедиться, что вы правильно настраиваете параметры проверки токена. Вот основные параметры, которые необходимо настроить:
- ValidateIssuer: Убедитесь, что это значение установлено в
true
. Это позволит проверять, что токен выдан доверенной сущностью. - ValidIssuer: Установите
ValidIssuer
в значение, которое соответствует вашему приложению. - ValidateAudience: Также это значение должно быть установлено в
true
, чтобы проверять целевую аудиторию токена. - ValidAudience: Установите
ValidAudience
в нужное значение.
Вот как может выглядеть исправленный код:
services.AddAuthentication()
.AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters() {
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Authentication:Jwt:Issuer"],
ValidAudience = Configuration["Authentication:Jwt:Audience"],
ValidateIssuer = true,
ValidateAudience = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Authentication:Jwt:IssuerSigningKey"]))
};
});
3. Настройка Swagger для обработки авторизации токенов
Вам также следует убедиться, что Swagger правильно обрабатывает заголовок авторизации. Ваш фрагмент кода для настройки Swagger выглядит корректно, однако важно проверить, что AddSecurityRequirement
настроен правильно:
c.AddSecurityRequirement(new OpenApiSecurityRequirement() {
{
new OpenApiSecurityScheme() {
Reference = new OpenApiReference() {
Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
Id = "JWT"
}
},
new string[0]
},
});
Это необходимо, чтобы Swagger отправлял токен в заголовке HTTP при выполнении API-запросов.
4. Тестирование и отладка
После внесения всех изменений, протестируйте работу авторизации в Swagger. Убедитесь, что:
- Ввод неверного токена приводит к ошибке авторизации.
- Вы можете успешно авторизоваться с корректным токеном.
Заключение
Реализация правильной авторизации — ключевая часть безопасности вашего приложения. Путем корректной настройки проверок токена JWT и правильной конфигурации Swagger, вы сможете устранить проблемы с авторизацией для вашего API. Не забывайте, что безопасность — это не только вопрос настройки, но и постоянного мониторинга и тестирования.