这个问题的灵感来自this SO question关于ASP.NET MVC中的访问控制。在这里,我试图将公认的答案转化为切实可行的解决方案。
答案提到使用 FileSystemSecurity
作为管理权限的灵感。在这里,我还使用带有 Flags 属性的枚举来定义所有对象的 ACL。此外,我的对象的每个成员都将存储在 SQL 中的列中。假设采用简化的 Linq2SQL、EF 或 nHibernate ORM 映射。
编辑:为此方法添加了以下好处/基本原理
此安全模型的灵感来自FileSystemRights,管理文件级权限的 .NET 方法。
我喜欢这种方法的主要原因之一是,我可以通过将所有单独的 ACL 组合在一起来轻松创建所有权限的摘要。我还喜欢可以添加 DENY ACL 来删除继承的权限。
List<myObject> PermissionsList = GetACLForObject(ItemID, UserID);
foreach (var acl in PermissionsList)
{
// The following enum has the [FlagsAttribute] applied so the .ToString() is pretty
PermissionWithFlagsEnum sampleForSO = (PermissionWithFlagsEnum )acl.Permission;
Console.Writeline ("Setting " + sampleForSO.ToString() + " permission for group: " + acl.ACLName);
ResultingPermission = resultPermission | acl.Permission ;
}
public int ResultingPermission {get;set;}
/End Edit
然后我想到,我可以通过特权用户的enum
的enum
数值来比较特权较低的用户。
我认为这个定义将允许快速、轻松地识别 SQL 数据库中的用户,而无需在后端解析枚举。 (通过从安全性的权限中选择用户来查找非特权用户)
这是我定义标志的方式(最低值具有最少权限)
[Flags]
public enum DBAccessRights
{
DenyAll =1 << 1,
WikiMode = 1 << 4,
AppearInListing = 1 << 8,
DetailRead = 1 << 12,
CreateNew = 1 << 18,
DetailEdit = 1 << 22,
DeleteChild = 1 << 26,
DeleteThis = 1 << 30,
EditPermissions = 1 << 31,
}
我有一个权限表,将用户 ID 连接到对象特定的 ACE。这应该会减少对特定行的并发更新的需要。
问题
This question is inspired by this SO question regarding Access Control in ASP.NET MVC. Here I'm trying to bring the accepted answer into a tangible solution.
The answer mentions using FileSystemSecurity
as an inspiration to managing permissions. Here I'm also using an enum with Flags attribute to define the ACL for all my objects. Additionally each member of my objects will be stored in a column within SQL. Assume a simplified Linq2SQL, EF, or nHibernate ORM mapping.
Edit: Added the following benefit / rationale for this approach
This security model was inspired by the FileSystemRights, the .NET approach to managing file level permissions.
One of the main reasons I like this approach is so I can easily create a summary of all the permissions by OR'ing all the individual ACLs together. I also like that I can add a DENY ACL to remove an inherited permission.
List<myObject> PermissionsList = GetACLForObject(ItemID, UserID);
foreach (var acl in PermissionsList)
{
// The following enum has the [FlagsAttribute] applied so the .ToString() is pretty
PermissionWithFlagsEnum sampleForSO = (PermissionWithFlagsEnum )acl.Permission;
Console.Writeline ("Setting " + sampleForSO.ToString() + " permission for group: " + acl.ACLName);
ResultingPermission = resultPermission | acl.Permission ;
}
public int ResultingPermission {get;set;}
/End Edit
Then it occurred to me that I could compare less privileged users by numeric value of the enum
from more privileged users's enum
.
I'm thinking that this definition would allow for quick and easy identification of users within the SQL database, without having to parse the enum on the backend. ( Find unprivileged users via select users from permissions where security < DBAceessRights.DetailRead
)
Here is how I defined the flags (lowest value has the least permission)
[Flags]
public enum DBAccessRights
{
DenyAll =1 << 1,
WikiMode = 1 << 4,
AppearInListing = 1 << 8,
DetailRead = 1 << 12,
CreateNew = 1 << 18,
DetailEdit = 1 << 22,
DeleteChild = 1 << 26,
DeleteThis = 1 << 30,
EditPermissions = 1 << 31,
}
I have a permission table that joins a user ID to an object specific ACE. This should reduce the need for concurrent updates on a particular row.
Question
-
Is this a good idea?
-
Has someone done it before (better than this)? (Edit: It is the accepted answer here )
-
If this is a standard way of implementing permissions, what is it called?
发布评论
评论(2)
我会说不。
您假设您需要不超过 32 个(如果使用
bigint
则为 64 个)权限。对我来说听起来像是一个任意的限制。转移到数据库中的varbinary
可以克服这个问题,但是你的枚举就完蛋了(无法在byte[]
上创建枚举)!而且您将无法进行数字比较。您假设权限
0x1
在逻辑上始终小于0x2
和0x80
等等。根据我的经验,这种情况很少见。更常见的是,权限是相互独立的(即:拥有“添加用户”权限与“上传图像”权限无关;一组用户(管理员)将拥有前者,而其他用户(内容发布者)则拥有“添加用户”权限)后者意味着您的数字比较并不像您最初想象的那么有用。您所做的可能会产生效果 。好处,但你还没有证明我的大多数权限系统都使用每个用户的一条数据库记录来授予或拒绝每个权限。获取 100 条记录不会对我的数据库造成负担。您最好在请求之间简单地缓存每个用户的权限,而不是使用位掩码。
关于更新性能:用户权限多久更改一次?一旦系统就位,根据我的经验,它们往往相当静态。
我发现当尝试将大量数据打包到一个小空间时,位掩码最有用。但当我最终得到超过 64 件事情时,我的第 1 点常常会回来咬我。
请注意,我在记录用户操作的统计信息时使用了此技术(跟踪用户在搜索中找到的项目、他们查看的实体等)。我的原因纯粹是为了确保数据库记录长度固定且较小,因此插入速度很快。而且我没有进行数字比较。 (而且,公平地说,我从未测试过
int
列和几个bit
列之间是否有任何区别)。编辑
一个基本的替代方案(我正在使用):用户和权限(我称之为权利)之间的 M:N 关系。
(对我的用户的米奇耳朵感到抱歉!)
中存在记录UserRight
表示该权限已授予该用户。缺席表明没有权利。此查询为您提供分配给用户的所有权限。然后,在代码中断言用户拥有一项权利:
显然,您可以扩展它以检查用户在一个查询中拥有多项权利。
这不会人为地限制您的系统支持的权限数量。而且它不假设权限之间存在任何关系(因此您无法进行数字比较;一切都是由我的系统中的
IEnumerable
完成的)。而且,如果您真的热衷于位掩码,您可以在缓存中创建一个Dictionary
!我还有一个角色的概念,它为用户提供了一组逻辑权限。这只是另一个 M:N 表。这是给读者的练习!
I'd say no.
You're assuming you'll need no more than 32 (or 64 if you use
bigint
) privileges. Sounds like an arbitrary limit to me. Moving to avarbinary
in the database can overcome this, but then your enum is up the creek (can't create enums onbyte[]
)! And you won't be able to do numeric comparisons.You're assuming that privilege
0x1
will always be logically less than0x2
and0x80
and so on. That's rarely the case in my experience. More usually, privileges are quite independent of each other (ie: having the "add user" privilege has nothing to do with the "upload image" privilege; one group of users (admins) will have the former while others (content publishers) have the latter. This means your numeric comparison isn't as helpful as you first thought.What you're doing may yield a performance benefit, but you haven't demonstrated there's a performance problem yet! Most of my privilege systems work with one database record per user to grant or deny each privilege. Fetching 100 records doesn't tax my database. You'd do better simply caching each user's permissions between requests than using a bitmask.
Regarding update performance: how often do user permissions change anyway? Once a system is in place they tend to be pretty static in my experience.
I've found bitmasks most useful when trying to pack alot of data into a small space. But often my point 1 comes back to bite me when I end up with more than 64 things.
Note that I have used this technique when recording statistics of user actions (tracking what items users find in searches, what entities they view, etc). My reason was purely to make sure database record lengths were fixed and small so inserts were fast. And I wasn't doing numeric comparisons. (And, to be fair, I never tested to see if there was any difference between an
int
column and severalbit
columns).EDIT
A basic alternative (which I'm using): an M:N relationship between Users and Privileges (I call them Rights).
(Sorry about those Micky Mouse ears on my user!)
The presence of a record in
UserRight
indicates the right is granted to that user. The absence indicates no right. This query gives you all the rights assigned to a user.Then, in code to assert a user has a right:
Obviously, you can scale this to check a user has several rights in one query.
This doesn't artificially limit how many Privileges your system supports. And it doesn't assume any relationships between Privileges (so you can't do your numeric comparisons; everything is done by
IEnumerable<Right>
in my system). And, if you're really keen on bitmasks, you could create aDictionary<User, BitArray>
in a cache!I also have the concept of a
Role
which provides a logical group of rights for users. It's just another M:N table. That's an exercise for the reader!一般来说,将多个值(标志)放入一个字段中是一个坏主意。
如果您计划审核这些数据,那么这是一个非常糟糕的主意,因为它很难有效地找出哪些标志在更新之间发生了变化。
即使您不进行审核也可能会遇到问题
许多简单的查询(DeleteThis 授权的用户)都不是
SARGable 因为您需要先执行按位运算
比较。
此外
从安全性
的权限中选择用户
可能不会返回正确的结果,因为DBAceessRights.DetailRead
出现在列表中& CreateNew
大于 DetailRead 但不大于打开 DetailRead。因此,您希望获得的好处可能无法获得
管理并发(ACL 的多个写入者)会更加困难,因为改变一个“逻辑值”实际上就是改变所有值。
In general putting multiple values (flags) into a single field is a bad idea.
If you're planning on auditing this data its a really bad idea since it because its hard to efficiently tease out which flags changed from update to update.
Problems you may encounter even if you don't do auditing
Many simple queries (what users the DeleteThis authroization) aren't
SARGable because you'll need to perform a bitwise operation before
a comparison.
Also
select users from permissions where security <
may not return the right results becauseDBAceessRights.DetailRead
AppearInListing & CreateNew
is greater than DetailRead but doesn'thave the DetailRead turned on. So the benefit you hoped for you may not get
Managing concurrency (multiple writers to ACL) is more difficult since mutating one "logical value" is actually mutating all the values.