前言
整理一下最近研究的一个ACL(访问控制层)的思路。
一般的ACL访问控制层的实现。
我们需要定义一堆的权限规则(rule),然后每个规则,对应1个或数个操作。
具体的规则(rule)还要跟具体的角色(role)绑定。
所以我们被逼需要建立一个存放rule的表,天长日久我们就会有数量可观的rule记录。
这里有两个非常烦人的地方:
- 规格非常的多,判断都需要加载一堆的rule。
- 判断非常的耗时,每判断一个规则都需要遍历规则数据。
所以有些人想出了解决方法,经典的例子就是*uix 系统的文件权限系统。
*uix们是怎么干的
他们首先规定了所有的文件有3中操作,读/写/执行, 然后再规定有3种类型的操作情况,
分别是:所有者可操作/所有组可操作/其他人可操作
我们在处理文件上传及创建目录的时候,经常会使用的到的0777 标识其实一个是描述了上面的信息,具体拆成二进制就是:
八进制位 | 7 | 7 | 7 | ||||||
访问类型 | 所有者 | 所有组 | 其他人 | ||||||
二进制位 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
操作 | 读 | 写 | 执行 | 读 | 写 | 执行 | 读 | 写 | 执行 |
这里牛逼的地方在于使用一个3位8进制数(0777)就已经完全标识出如此复杂的权限标识。
而且具体的规则判断,更可以使用位操作这种底层运算实现,要知道位运算是相当快的。
例如我需要判断 所有者对文件有木有读权限,
我只需要将文件标识 flag (777)& 100 == 100 就可以了。
二进制版本 111|111|111 & 001|000|000 == 001|000|000
好了,废话就说这么多……其实介绍要告诉你如果用二进制位组织权限系统是非常紧凑及高效的。
改造方案1
参考了这种二进制权限方案后,我们可以来一个高仿,将所有的权限规则,编码成二进制。
例如:
权限规则 | 十进制 | 二进制 |
新建会员 | 1 | 1 |
删除会员 | 2 | 10 |
新建产品 | 4 | 100 |
合并规则 | 7 | 111 |
留意最后一行合并的结果,这代表,我们需要使用一个十进制数字 7 就是表示同时 拥有 新建会员/删除会员/新建产品 的权限
这方案显得相当的紧凑。
不过这个方案有个很大的问题!
我们是用位操作对于整形(integer)的,而整形在PHP 有边界,就是PHP能处理多大的一个整数呢。
经过查实,我们可以用PHP的一个内建常量PHP_MAX_INT ,一般会输出 2147483647 ,
这数究竟代表啥呢,其实这个数是 231 – 1,
就是说,我们最多能用这种方法定义 32个权限规则……Orz,32个根本不够用啊……
改进方案2
之后我们开始着手扩充这个限制,其中之一的办法就是将权限规则分组,
不过……这种方法其实跟上文第一种的一般方案没有太大的区别,还是要不断的遍历。
所有我们开始寻找代替整形(integer)的载体,然后我们发现,其实PHP是支持字符串的位操作的……
ref:http://php.net/manual/zh/language.operators.bitwise.php
留意里面的一句话,
要注意数据类型的转换。如果左右参数都是字符串,则位运算符将对字符的 ASCII 值进行操作。
OMG……原来PHP支持对字符串的位运算!好了我们现在继续使用之前的方案了,不过载体换成了字符串。
上面的7我们怎样标识呢?—— “111” 是不是很傻……没办法,PHP的字符串位操作是对应string中的ASCII码的。
‘1’ 的ASCII码是 49,二进制 0011 0001;‘0’的ASCII码是 48,二进制 0011 0000;
“1” | “0” = “1”, “1” & “0” = “0”
这还不是最悲剧的,更悲剧的是,我们位操作需要两边等长度,不然会位数对不上会出现意想不到的结果。
所以我们需要前缀补位……
“111” & “010” == “010” //判断是否获得“删除会员”权限
不管怎么说,现在我们还是得到了一个无限可扩充的紧凑的权限系统了……
最后成品貌似应该像这样子:
<?php class ManagerRole { //资源标识 const RESOURCES_ADMIN = '1', //管理员管理 RESOURCES_UPLOAD = '10', //上传文件管理 RESOURCES_MERCHANT = '1000', //商户管理 RESOURCES_CATEGORY = '10000', //分类管理 RESOURCES_ATTR = '100000', //属性管理 RESOURCES_PRODUCT = '1000000', //产品管理 RESOURCES_ORDER = '10000000', //订单管理 RESOURCES_ROLE = '100'; //上传文件管理 /** * 获取资源列表 */ static public function get_resource() { $class = new ReflectionClass('ManagerRole'); $consts = $class->getConstants(); $keys = array(); foreach($consts as $key => $row) { if (stripos($key, 'resources_')!== false) { $keys[$key] = $row; } } unset($class); return $keys; } /** * 获取资源列表 * @param $rule 角色权限 * @param $resource 访问资源标识 * @return bool */ static public function check_rule($rule, $resource) { //获取最长字符长度 $lengths = array_map('strlen', array($rule, $resource)); $count = max($lengths); //为不够长的字符串补位 $rule = str_pad($rule, $count, '0', STR_PAD_LEFT); $resource = str_pad($resource, $count, '0', STR_PAD_LEFT); return (($rule & $resource) == $resource); } }
发表评论
要发表评论,您必须先登录。