Создание кастомного AuthorizeAttribute в ASP.NET Core: пошаговое руководство
Правильный подход к кастомной авторизации в ASP.NET Core
В ASP.NET Core архитектура авторизации полностью изменилась по сравнению с классическим ASP.NET MVC. Вместо переопределения методов в атрибуте, рекомендуется использовать политики авторизации (Authorization Policies) в связке с обработчиками требований (Authorization Handlers). Это даёт гибкость, тестируемость и возможность внедрения зависимостей (DI).
Вот пошаговое решение для вашей задачи с проверкой разрешения через сервис.
Шаг 1. Определите требование (Requirement)
Создайте класс, реализующий интерфейс IAuthorizationRequirement. Он просто хранит данные, которые нужны для проверки (например, имя разрешения).
public class PermissionRequirement : IAuthorizationRequirement
{
public string PermissionName { get; }
public PermissionRequirement(string permissionName)
{
PermissionName = permissionName;
}
}
Шаг 2. Создайте обработчик (AuthorizationHandler)
Здесь находится логика проверки. Обработчик может использовать DI для получения любых сервисов (например, IPermissionService).
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly IPermissionService _permissionService;
public PermissionHandler(IPermissionService permissionService)
{
_permissionService = permissionService;
}
protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
PermissionRequirement requirement)
{
// Получаем ID пользователя из claims (например, NameIdentifier)
var userIdClaim = context.User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim == null)
{
context.Fail();
return;
}
var userId = Guid.Parse(userIdClaim.Value);
bool hasPermission = await _permissionService.UserHasPermissionAsync(userId, requirement.PermissionName);
if (hasPermission)
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
}
}
Шаг 3. Зарегистрируйте политику и обработчик в Program.cs
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("CanEditProducts", policy =>
policy.Requirements.Add(new PermissionRequirement("CanEditProducts")));
});
// Регистрируем обработчик как singleton или scoped
builder.Services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
// Ваш сервис проверки разрешений
builder.Services.AddScoped<IPermissionService, PermissionService>();
Шаг 4. Используйте атрибут [Authorize] с политикой
Теперь вы можете применить политику к контроллеру или экшену:
[Authorize(Policy = "CanEditProducts")]
public IActionResult EditProduct(int id)
{
// Ваша логика
return View();
}
Как сделать атрибут с параметром (более удобный синтаксис)
Если хотите использовать атрибут как [RequirePermission("CanEditProducts")], создайте свой атрибут, который реализует IAuthorizationFilter или использует фабрику политик. Вот простой способ через фабрику:
public class RequirePermissionAttribute : AuthorizeAttribute
{
public RequirePermissionAttribute(string permission)
: base(policy: permission) // Название политики = название разрешения
{
}
}
// В Program.cs регистрируем динамическую политику:
builder.Services.AddAuthorization(options =>
{
// Можно не регистрировать каждое разрешение вручную,
// а использовать AuthorizationPolicyBuilder в обработчике
});
// Или более продвинутый вариант — через IAuthorizationPolicyProvider
// (см. документацию Microsoft)
Важные моменты
- Не используйте переопределение
AuthorizeAttributeнапрямую — это нарушает принципы DI и тестируемости. - Обработчик может быть асинхронным (
async Task), что отлично для работы с БД. - Можно комбинировать несколько политик:
[Authorize(Policy = "CanEditProducts")]+[Authorize(Roles = "Admin")]— обе должны быть выполнены. - Для возврата 403 вместо редиректа на логин настройте
CookieAuthenticationOptions.AccessDeniedPath.
Этот подход официально рекомендуется Microsoft и используется в production-приложениях. Он легко масштабируется: вы можете добавить проверку нескольких разрешений, работу с ролями, claims и т.д.
<<Альтернативный подход: фильтр авторизации (IAuthorizationFilter)
Если вам нужно что-то более простое и вы не хотите углубляться в систему политик, можно использовать фильтр, реализующий IAuthorizationFilter. Однако этот метод менее гибкий и не поддерживает асинхронность на уровне фреймворка.
public class PermissionFilter : IAuthorizationFilter
{
private readonly string _permission;
private readonly IPermissionService _permissionService;
public PermissionFilter(string permission, IPermissionService permissionService)
{
_permission = permission;
_permissionService = permissionService;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var userIdClaim = context.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim == null)
{
context.Result = new ForbidResult();
return;
}
bool hasPermission = _permissionService.UserHasPermissionAsync(
Guid.Parse(userIdClaim.Value), _permission).GetAwaiter().GetResult();
if (!hasPermission)
{
context.Result = new ForbidResult();
}
}
}
// Использование через атрибут:
public class RequirePermissionAttribute : TypeFilterAttribute
{
public RequirePermissionAttribute(string permission)
: base(typeof(PermissionFilter))
{
Arguments = new object[] { permission };
}
}
// Регистрация в Program.cs:
builder.Services.AddScoped<IPermissionService, PermissionService>();
Недостатки: синхронный вызов GetAwaiter().GetResult() может привести к deadlock в некоторых сценариях, сложнее тестировать. Рекомендую использовать первый вариант с политиками — он более современный и надёжный.
Ответов пока нет.
Войдите чтобы оставить ответ