Разрешения на страницу в ASP.NET Core - PullRequest
0 голосов
/ 13 января 2019

У меня есть сценарий, который не поддерживают роли и утверждения, поэтому я пошел по пути реализации этого сценария, и я хотел бы задать несколько вопросов и сказать мне, является ли то, что я делаю, правильным способом сделать это или предложить другой способ реализовать это. Прежде всего я хочу определить разрешение каждой страницы Controller / Action или Razor, используя такой атрибут:

[Data.CheckAccess(PermissionsEnum.Users_Create)]
public class PrivacyModel : PageModel
{
    public void OnGet()
    {
    }
}

PermissionsEnum имеет следующую форму:

public enum PermissionsEnum
{
    Users_View = 101,
    Users_Create = 102,
    Users_Edit = 103,
    Users_Delete = 103,
    Users_Details = 104,

    Products_View = 201,
    Products_Create = 202,
    Products_Edit = 203,
    Products_Delete = 204,
    Products_Details = 205
}

Я изменил IdentityRole, чтобы иметь возможность прикреплять список разрешений для каждой роли.

public class ApplicationRole : IdentityRole
{
    public string Permissions { get; set; }

    public void SetPermissions(List<PermissionsEnum> permissions)
    {
        Permissions = Newtonsoft.Json.JsonConvert.SerializeObject(permissions);
    }

    public List<PermissionsEnum> GetPermissions()
    {
        return Newtonsoft.Json.JsonConvert.DeserializeObject<List<PermissionsEnum>>(Permissions);
    }
}

EntityFramework не может иметь свойства типа List, поэтому я использовал строковое свойство и у меня есть два вспомогательных метода для сериализации и десериализации списка enum. Теперь я могу создавать страницы администратора, чтобы пользователь мог создавать роли и проверять разрешения, сгруппированные по пользователям, и продукты (это две группы, которые у меня есть в перечислителе) для более удобного управления разрешениями. После создания ролей я смогу использовать другую страницу для назначения ролей пользователям. Я создал следующий атрибут, который сделает авторизацию:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class CheckAccessAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    private PermissionsEnum permission;

    public CheckAccessAttribute(PermissionsEnum permission)
    {
        this.permission = permission;
    }

    public async void OnAuthorization(AuthorizationFilterContext context)
    {
        if (!context.HttpContext.User.Identity.IsAuthenticated)
        {
            return;
        }

        UserManager<ApplicationUser> userManager = (UserManager<ApplicationUser>)context.HttpContext.RequestServices.GetService(typeof(UserManager<ApplicationUser>));
        var user = await userManager.GetUserAsync(context.HttpContext.User);
        RoleManager<ApplicationRole> roleManager = (RoleManager<ApplicationRole>)context.HttpContext.RequestServices.GetService(typeof(RoleManager<ApplicationRole>));

        var roles = await userManager.GetRolesAsync(user);

        foreach (var role in roles)
        {
            var CurrentRole = await roleManager.FindByNameAsync(role);

            if (CurrentRole.GetPermissions().Contains(permission))
                return;
        }

        // the user has not this permission
        context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
        return;
    }
}

Это работает нормально, но когда я запускаю приложение, я вхожу в систему и останавливаю приложение, и после этого я запускаю приложение, пользователь входит в систему, потому что есть куки-файл аутентификации. В результате произойдет сбой приложения. Сообщение об ошибке, когда я пытаюсь получить пользователя с помощью usermanager. Сообщение об ошибке следующее.

System.ObjectDisposedException: 'Невозможно получить доступ к удаленному объекту. Распространенной причиной этой ошибки является удаление контекста, который был разрешен путем внедрения зависимости, а затем попытка использовать тот же экземпляр контекста в другом месте вашего приложения. Это может произойти, если вы вызываете Dispose () для контекста или заключаете контекст в оператор using. Если вы используете внедрение зависимости, вы должны позволить контейнеру введения зависимости позаботиться об удалении экземпляров контекста. Имя объекта: 'AsyncDisposer'. '

Итак, я изменил свой код, чтобы обойти эту проблему, и теперь код следующий.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class CheckAccessAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    private PermissionsEnum permission;

    public CheckAccessAttribute(PermissionsEnum permission)
    {
        this.permission = permission;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        if (!context.HttpContext.User.Identity.IsAuthenticated)
        {
            return;
        }

        var userId = context.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;

        ApplicationDbContext DbContext = (ApplicationDbContext)context.HttpContext.RequestServices.GetService(typeof(ApplicationDbContext));

        var user = DbContext.Users.Where(m => m.Id == userId).FirstOrDefault();
        var RoleIDs = DbContext.UserRoles.Where(m => m.UserId == user.Id).Select(m => m.RoleId);
        var Roles = DbContext.Roles.Where(m => RoleIDs.Contains(m.Id)).ToList();
        foreach (var role in Roles)
        {
            if (role.GetPermissions().Contains(permission))
                return;
        }

        // the user has not this permission
        context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
        return;
    }
}

У меня есть следующие вопросы.

  1. Есть ли лучший способ реализовать этот сценарий?
  2. Почему возникает исключение System.ObjectDisposedException. Есть ли способ решить эту проблему?
  3. Чтобы улучшить производительность, я попытаюсь кэшировать разрешения текущего пользователя, чтобы мне не приходилось загружать их каждый раз, когда атрибут используется страницей. Я думаю, что я буду использовать метод кэширования в памяти (https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-2.2). Это лучший способ сделать это?

1 Ответ

0 голосов
/ 13 января 2019

Некоторые предложения:

  • Посмотрите: Как создать собственный атрибут AuthorizeAttribute в ASP.NET Core? .
  • Поскольку вы просто повторяете действия (просмотр, создание, редактирование, удаление, детали) для каждой контролируемой правами доступа "зоны" (пользователь, продукт, ...), рассмотрите возможность разделения этих "зон" и разрешений в отдельных файлах. Затем в своем атрибуте вы можете написать [CheckAccess(user, create)] Делает также проще для внешнего интерфейса устанавливать разрешения.
  • Используйте HasConversion для сериализации разрешений.
  • «Я думаю, что я буду использовать метод кэширования в памяти» - если вы кэшируете разрешения, не забывайте аннулировать этот кеш каждый раз, когда вы меняете разрешения пользователя или добавляете новые разрешения.
  • Не используйте async void, кроме как для асинхронных обработчиков событий. Скорее всего, это причина того, что вы испытываете ObjectDisposedException, потому что ваш метод выполняется асинхронно, а вызывающий процесс не ожидает завершения асинхронной операции и удаляет контекст БД. Вместо этого используйте асинхронную версию IAuthorizationFilter . Посмотрите на этот ответ: https://stackoverflow.com/a/53856127/2477619
...