权限原理
- 从代码层面来讲,就是记录一个角色与一个控制器和控制器所有方法的关系。举个例子,管理员角色拥有用户控制器中添加用户、删除用户等方法的访问权限。假设一个控制器对应一个菜单,控制器的方法就是菜单的操作,这里用户菜单的 id 设为 1,添加用户这个操作标记为 1,删除用户操作标记为 2,更新用户操作标记为 4,依次类推标记所有操作为 2 的 n 次方
- 当然用什么标记可以自己定,这里只是让他们组成的列表符合位域的设计,即 2 的幂(即 1、2、4、8 等)。记录方式实际上就是将用户 id(这里设管理员 id 为 1)、菜单 id、操作标记、是否授权等这个几个属性记为一条数据。每次访问的时候,就根据当前用户和访问的菜单和操作,查询是否有授权,即可实现权限管理功能
- 比如,管理员角色对于用户菜单的添加、删除、更新操作,具有权限访问,这些数据记录为
角色 id | 菜单 id | 操作 id | 是否授权 |
1 | 1 | 1 | 是 |
1 | 1 | 2 | 是 |
1 | 1 | 4 | 是 |
- 当使用位域的方式记录所有操作时,比如同时授权添加、删除、更新这三个操作,那么就是
1 + 2 + 4 = 7
,二进制即001 + 010 + 100 = 111
,也即1|2|4 = 7
,每添加一个操作,直接用当前记录值和操作标记进行或运算。因为每个操作标记对应的二进制都是只有一个 1,而且位置不同,所以加起来不会产生进位,结果相当于累加(没有进位)。那么上述表记录变成
角色 id | 菜单 id | 操作 id |
1 | 1 | 7 |
- 那么怎么知道哪个操作被授权了呢?哪个位置上是 1 就说明哪个操作被授权。而判断方法就是与运算,需要判断的操作和记录值进行与运算,两个位置都是 1 的,结果对应位置才是 1,其余位置都是 0。比如,判断删除操作是否被授权,2 对应二进制 10,和记录值 7 的二进制 111 进行与运算,结果得 010。但如果,10 不在记录值里面,即 101,那么与运算结果为 0。
2 & 7 = 2 -> 10 & 111 = 10
,10 & 101 = 0
- 总的来说就是,添加操作就是或运算,判断操作就是与运算,那去掉操作呢?那也简单,实际上直接减去就行,对应的二进制运算叫做异或,也叫半加运算,没有进位的加法。比如
111 ⊕ 10
,10 加上去之后没有进位,结果是 101,相当于去掉了操作,大部分高级语言用的异或符号是^
权限使用与菜单生成
- 上面原理说起来也简单,说白了就是两个二进制操作,使用位域把记录简化。后来实际应用的时候,就将一个角色的所有菜单的操作权限全部合在一起,作为角色的一个字段,这个字段的值类似于
1#255,2#255,3#255,4#255
。设这个字段为Permission
,逗号分隔每个菜单,每个记录是菜单id#操作记录值
。上述值就是 1-4 这个几个菜单的操作值,都是 255,即1|2|4|8|16|32|64|128
,默认可容纳 8 个操作 - 那么如何将控制器与菜单和操作关联起来呢?答案是特性。使用时通过在方法添加特性标记这个方法,代码就会通过反射将每个控制器生成一个菜单,方法生成对应菜单的操作,并记录标记值。
/// <summary>表单,添加/修改</summary> /// <returns></returns> [EntityAuthorize(PermissionFlags.Insert)] [DisplayName("添加{type}")] public virtual ActionResult Add(TEntity entity) { entity.Save(); return View(); }
- 扫描控制器入口在的RegisterArea方法。每个区域都要继承此基类并调用该方法注册。
权限判断
- 权限判断的时候,也是通过这个特性获取当前方法的操作标记值,找到对应的菜单,再比较操作值
- 代码详见AuthorizeAttribute。该特性会在系统进行授权的时候被调用。
角色菜单
- 在后台点击角色菜单,可以看到几个默认角色
- 菜单由系统根据控制器自动生成
- 点击角色列表的编辑按钮,进入权限编辑页面
- 每个角色被授予什么权限,在这里编辑,保存后会更新到相应菜单的权限属性。
- 如果是更复杂的权限,需要自行编写代码实现。