代码之家  ›  专栏  ›  技术社区  ›  pitaridis

ASP.NET核心中每页的权限

  •  1
  • pitaridis  · 技术社区  · 6 年前

    我有一个角色和声明不支持的场景,所以我遵循了实现这个场景的路径,我想问一些问题,并告诉我我做什么是正确的方法来实现它,还是建议一种不同的方法来实现它。 首先,我想使用如下属性定义每个控制器/操作或Razor页面的权限:

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

    许可证的格式如下:

    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类型的属性,因此我使用了一个字符串属性,并且有两个助手方法来序列化和反序列化枚举列表。 现在,我可以创建管理员页面,用户可以创建角色并检查按用户分组的权限,以及产品(这是我在枚举器中拥有的两个组),以便更轻松地管理权限。 创建角色后,我将能够使用其他页面为用户分配角色。 我创建了以下属性来进行授权:

    [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;
        }
    }
    

    这很好,但是当我启动应用程序时,我会登录并停止应用程序,然后启动应用程序,用户会登录,因为存在一个身份验证cookie。这将导致应用程序崩溃。当我尝试使用用户管理器获取用户时,会出现错误消息。错误消息如下。

    System.Object处理异常: '无法访问已释放的对象。此错误的一个常见原因是处理从依赖项注入中解决的上下文,然后在应用程序的其他地方尝试使用相同的上下文实例。如果在上下文中调用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 回复  |  直到 6 年前
        1
  •  1
  •   Felix K.    6 年前

    一些建议:

    • 看看: How do you create a custom AuthorizeAttribute in ASP.NET Core? .
    • 由于您只是为每个访问控制的“区域”(用户、产品,…)重复操作(查看、创建、编辑、删除、详细信息),请考虑在单独的文件中分离这些“区域”和权限。在你的属性中你可以写 [CheckAccess(user, create)] 使前端更容易设置权限。
    • 使用 HasConversion 用于权限的序列化。
    • “我认为我将使用缓存内存方法”如果您缓存权限,请确保每次更改用户权限或添加新权限时,都要记住使此缓存无效。
    • Do not use async void 除非用于异步事件处理程序。这很可能是您体验 ObjectDisposedException 因为您的方法是异步执行的,而调用进程不等待异步操作完成并释放DB上下文。使用 async version of the IAuthorizationFilter 相反。看看这个答案: https://stackoverflow.com/a/53856127/2477619
    推荐文章