ASP.NET 核心中每页的权限

Permissions per page in ASP.NET Core

我有一个角色和声明不支持的场景,所以我遵循了实现这个场景的路径,我想问一些问题并告诉我我所做的是否是正确的方法或建议不同的方式来实现它。 首先,我想通过使用这样的属性来定义每个 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 类型的属性,所以我使用了一个字符串 属性 并且我有两个辅助方法来序列化和反序列化枚举列表。 现在我可以创建管理页面,用户可以在其中创建角色并检查按用户和产品分组的权限(这是我在枚举器中拥有的两个组),以便更轻松地管理权限。 创建角色后,我将能够使用其他页面为用户分配角色。 我创建了以下将进行授权的属性:

[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。这将导致应用程序崩溃。错误消息是当我尝试让用户使用 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)。这是最好的方法吗?

一些建议:

  • 查看:
  • 由于您只是为每个访问受控 "zone"(用户、产品等)重复操作(查看、创建、编辑、删除、详细信息),请考虑将这些 "zones" 和单独文件中的权限。然后在你的属性中你可以写 [CheckAccess(user, create)] 让前端更容易设置权限。
  • 使用HasConversion进行权限序列化。
  • "I think I will use the Cache in-memory method" – 如果您缓存权限,请务必记住在每次更改用户权限或添加新权限时使此缓存无效。
  • async void unless for asynchronous event handlers. This is most likely the reason you are experiencing the ObjectDisposedException because your method is executed async and the calling process does not wait for the async operation to finish and disposes the db context. Use the async version of the IAuthorizationFilter instead. Have a look at this answer: