我有一个角色和声明不支持的场景,所以我遵循了实现这个场景的路径,我想问一些问题,并告诉我我做什么是正确的方法来实现它,还是建议一种不同的方法来实现它。
首先,我想使用如下属性定义每个控制器/操作或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;
}
}
我有以下问题。
-
有没有更好的方法来实现这个场景?
-
发生异常System.ObjectDisposedException的原因。有没有办法解决这个问题?
-
为了提高性能,我将尝试缓存当前用户的权限,这样我就不必每次页面使用属性时都加载这些权限。我想我会使用缓存内存方法(
https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-2.2
)这是最好的方法吗?