Но вообще,
механизм классный, но у него есть один подводный камень, не совсем очевидный, скажем так. И изначально про него и хотел рассказать, но сначала нужно было погрузить в контекст, да и мало ли, кто-то вдруг не знает, что механизм авторизации чуть интереснее чем аттрибуты и мидлвари. Так вот.
Есть метод
Task AuthorizeAsync(
ClaimsPrincipal user,
object? resource,
string policyName);
Мы передаем Пользователя, какой-то ресурс для определения прав и имя политики, чтобы понять что нам вообще нужно проверить. И вот в хэндлере мы делаем такой гибкий механизм
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
AuthorizationRequirement.CanViewVacancyRequirement requirement,
Vacancy resource)
{
if (context.User.IsInRole("Admin") )
{
_logger.LogInformation("User {UserName} has admin role", context.User.Identity.Name);
context.Succeed(requirement);
return Task.CompletedTask;
}
…
If (resource.Initiator?.InitiatorCodeAd == userAdCode)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
_logger.LogInformation("Доступ запрещен: пользователь {UserName} не имеет прав", context.User.Identity?.Name);
return Task.CompletedTask;
}
Вроде все выглядит логично – проверили сначала базовые статичные правила, например клеймы из контектса, потом смотрим на ресурс и поределяем доступ для конкретного пользователя. Логика простая, админа мы всегда пустим, а дальше по обстоятельствам.
Представьте мое лицо, когда меня, как админа, не пустило в систему по запрошенному ресурсу? Я сто раз перепроверил токен, клеймы, правила – все как должно быть, но не пускает. В логах ни ошибки, ни исключения, вообще ничего – просто отказ и все. Знаете в чем дело? Был по ошибке передан не правильный объект, другого типа, а не тот что ожидался. Из-за того, что метод принимает object, то есть вообще все, что угодно, никаких ошибок при билде не было, а то что не удалось объект привести к нашему типу – фреймворк просто отбрасывает такой запрос, не выбрасывает наверх исключение, не логирует попытку, просто отклоняет запрос и все. Но казалось бы, причем тут объект, ведь мы ранее в методе проверяем базовые роли, не обращаемся к объекту, мы заранее можем знать можно ли пропустить пользователя или нет? Но вот в этом и есть особенность данного метода, под капотом сначала собирается контекст, потом передается в связанный хэндлер, а внутри уже хэндлера идет приведение
if (context.Resource is TResource resource)
{
foreach (var req in context.PendingRequirements.OfType())
{
await HandleRequirementAsync(context, req, resource).ConfigureAwait(false);
}
…
}
Тип не подходит, метод отрабатывает впустую, результата нет, нет результата – значит и доступа нет. И поэтому даже не будет проверки ролей, клеймов и т.д., что мы укажем до обращения к ресурсу. Вот так вот бывает.
Ну и что делать с этой информацией? Стараться разделять типы проверок, и те которым конкретный ресурс не нужен, выносить в отдельные хэндлеры. Другой вариант – это написать обертку, и закрыть ее дженериком
public static Task AuthorizeResourceAsync( this IAuthorizationService service, ClaimsPrincipal user, T resource, string policyName) where T : IHasCustomAccess { }
Тогда неподходящий тип не получится передать, ну если только не вызывать напрямую, минуя наше расширение.