Зачем вам вообще использовать встроенную ролевую авторизацию в вашем API?
[HttpGet("get_something")] [Authorize(Role = "admin, admins_cat")] [Authorize(Role = "hacker")] public Task<IActionResult> GetSomething()
Хорошо, это выглядит красиво и хорошо работает, но в случае большого SaaS-приложения вы теряете некоторую гибкость. Представим, что у вас есть 3 основные роли: StandartUser, PremiumUser и EnterpriseUser. Вы написали множество функций и сотни конечных точек. И сейчас самое время расширить список подписчиков. Вы хотите добавить роль UltraUser, такой пользователь будет иметь полный доступ ко всем функциям. Итак, теперь вам следует поместить это во все свое приложение.
[Authorize(Role = "UltraUser")]
Я предпочитаю использовать флаги перечисления для ролей, разрешений и т. д. Давайте углубимся в эту тему.
Мы создаем новый PermissionEnum со всеми функциями, которые наше приложение предоставляет пользователю. Например, у нас есть: «Добавить», «Обновить», «Удалить», «Получить» и «Специальные функции». Это будет выглядеть так.
[Flags] public enum PermissionEnum : int { None = 0, Add = 1, Update = 1 << 1, Delete = 1 << 2, Get = 1 << 3, Special = 1 << 4 }
Если вы не знакомы с флагами перечисления, вам следует потратить некоторое время на чтение этого:
Для подписок мы также будем использовать перечисление. Это дало бы нам возможность создавать разрешения. По этой причине создается PermissionService. Вся логика разрешений и подписок находится здесь.
public enum RoleEnum { Standart = 1, Premium = 2, Ultra = 3, Admin = 4 } public class PermissionService { public PermissionEnum GetPermissions(RoleEnum role) { var permissionsList = PermissionEnum.None; switch (role) { case RoleEnum.Admin: permissionsList = permissionsList | PermissionEnum.Get | PermissionEnum.Delete | PermissionEnum.Add | PermissionEnum.Update | PermissionEnum.Special; break; case RoleEnum.Ultra: permissionsList = permissionsList | PermissionEnum.Get | PermissionEnum.Delete | PermissionEnum.Add | PermissionEnum.Update; break; case RoleEnum.Premium: permissionsList = permissionsList | PermissionEnum.Get | PermissionEnum.Add | PermissionEnum.Update; break; case RoleEnum.Standart: permissionsList = permissionsList | PermissionEnum.Get | PermissionEnum.Add; break; } return permissionsList; } }
Итак, как нам следует использовать эту услугу?
Когда токен доступа создан, мы помещаем PermissionEnum в полезную нагрузку JWT. Тогда этот PermissionEnum будет доступен в каждом запросе.
_tokenService.CreateToken(user.Id.ToString(), _permissionService.GetPermissions(user.Role));
Пришло время добавить атрибут, отвечающий за разрешения. Вы можете расширитьAuthorizeAttribute,
, но для простоты я напишу свой собственный.
public class AllowedForAttribute : Attribute { public IEnumerable<PermissionEnum> AllowedFor { get; set; } public AllowedForAttribute(params PermissionEnum[] rights) { AllowedFor = rights; } }
Последний шаг — добавить промежуточное программное обеспечение, которое будет проверять, есть ли у пользователя все необходимые разрешения для доступа к определенной конечной точке.
public class EndpointPermissionMiddleware : IMiddleware { public async Task InvokeAsync(HttpContext context, RequestDelegate next) { //get and parse JWT to get PermissionEnum var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint; CheckIfAllowed(endpoint, userPermissions); await next.Invoke(context); } private void CheckIfAllowed(Endpoint? endpoint, PermissionEnum permissions) { var attribute = endpoint?.Metadata.GetMetadata<AllowedForAttribute>(); if (attribute is not null && !IsAllowed(attribute.AllowedFor, permissions)) { throw new UnauthorizedAccessException(); } } private bool IsAllowed(IEnumerable<PermissionEnum> rights, PermissionEnum permissions) { foreach (var right in rights) { if (permissions.HasFlag(right)) { return true; } } return false; }
Краткое содержание
Отличная работа! Теперь вы можете легко расширять и изменять свои подписки. Вы можете использовать функциональные возможности разрешений следующим образом:
[AllowedFor(PermissionEnum.Get | PermissionEnum.Delete | PermissionEnum.Add | PermissionEnum.Update, PermissionEnum.Special)] public IEnumerable<WeatherForecast> Ultra()
Надеюсь, вам понравилась эта идея! Видите ли вы какие-либо проблемы в этом потоке?
Код можно получить здесь.