权限管理在一个系统中是不可或缺的,总的来说还是一个数学的问题。
之前这个系统的权限管理是通过配置文件来处理的,大概流程是这样的,把用户分成多个用户组,然后每个用户组对应着多个用户的 id,每次访问页面的时候,都会读取这个配置文件的信息,判断登录用户的 id 属于哪个用户组,然后在页面判断这个用户组是否有访问这个链接的权限。配置文件的格式是这样的: {"adm" : [1,2,34], "dev" : [5,6,1]}
这样会带来什么问题呢?有以下几个:
这是没有应用一点的数学方法来解决,等于是写死,故称为“笨方法”。
能不能有聪明点的方法呢?这是肯定的。这个问题可以归纳为——如何用占位最少的状态,包含多种的子状态? 我们每次查询权限和维护时,都对这个资源的权限内容与用户的权限进行运算,便可以得知最终是否拥有权限去执行下一步;维护的时候也是,无非是增加权限和删除权限,却又不影响其他的权限。8421 码就是一种简单的解决方案,而且它是最基础的权限算法,必须掌握。
8421码,又称为 BCD 码。参见 《简单权限控制-8421法则》
、 《“与”和“或”运算实现权限管理》
,这里主要是使用到 &
位与运算符的操作,而文中虽然提到“ |
位或运算符”,但实际更便于我们理解的说法,是十进制的加法操作。 2 | 4 =6
即是 2 + 4 = 6
。
8421 码缺点是权限数量有限,当权限越多,那么权限会越来越大,为 2 的 n 次方全部相加,维护起来不仅存储占空间,而且运算效率也不高。例如这文章和 这篇文章 介绍的。
对此,我们想到了二进制的解决方法。我们知道,权限状态无非就两有,即有或无,所以可以用 boolean 值来保存,而 boolean 类型可以用“位 Bit”来保存。Java 中 Long 类型占 8 个字节,那么 Long 其实能够保存 64 个 boolean 值,即可保存 64 个操作项的权限。
在“用户”表中保存这个 Long 值,需要将第 N 个操作权限设置为 true
时,只需从右向左数到第 N 位,将其值设为 1 即可,反之设为 0;需要检查第 N 位的权限值时,只需将 long 值右移 N 位,再 &1
,即可得到权限值。本框架的权限算法就是采用这个的。如果对于移位不理解,其实无所谓,因为我们已经封装好,看如何调用实现即可(除非你想知其所以然,就要另请高贤了)。
试用 JavaScript 写出实现如下。
/** * 检查是否有权限 * @return {Boolean} true = 有权限,反之无 */ function check(num, pos) { num = num >>> pos; return (num & 1) === 1; } /** * 设置权限 */ function set(num, pos, v) { var old = check(num, pos); if (v) {// 期望改为无权限 if (!old) // 原来有权限 num = num + (1 << pos);// 将第 pos 位设置为 1 } else {// 期望改为有权限 if (old) // 原来无权限 num = num - (1 << pos);// 将第 pos 位设置为 0 } return num; } var num = 0; num = set(num, 1, true);//设置[权限项60]为true num = set(num, 3, true);//设置[权限项3]为true num = set(num, 5, true);//设置[权限项3]为true num = set(num, 6, true);//设置[权限项3]为true num = set(num, 8, true);//设置[权限项3]为true alert(check(num, 60));//检查[权限项60]的权限 alert(check(num, 1));//检查[权限项1]的权限 alert(check(num, 3));//检查[权限项3]的权限
算法出处参见 《两种简单权限算法(二)》 ,还有一种复杂点的。 《权限设计[摘录]》 也是不错的文章。
TODO
用户、角色和权限开发
在接触 8421 码之前,我还了解过“质因数分解”的算法。利用“唯一分解定理”:任何一个大于 1 的自然数 N,如果 N 不为质数,那么 N 可以唯一分解成有限个质数的乘积,唯一分解定理。例如用质数2、3、 5、7、11…组成权限集合,某用户的权限为其子集中各整数的乘积,如 210 = 2 3 5*7。但问题仍然是乘积会随着权限增多而变得很大。于是放弃了。
现在写过的代码保存于此。
package com.ajaxjs.user.role; import java.util.*; public class RoleUtil { /** * 分析这个数是不是质数 * * @param num */ public static boolean isZhishu(int num) { switch (num) { case 1: case 2: case 3: return true; } int temp = 0; for (int i = 2; i < num / 2 + 1; i++) { if (num % i == 0) { temp++; break; } } if (temp != 0) return false; return true; } /** * 得到一个数所有的因数 * * @param num * @return */ public static List<Integer> zhengChu(int num) { List<Integer> integers = new ArrayList<>(); for (int i = 2; i < num / 2; i++) { if (num % i == 0) integers.add(i); } return integers; } /** * 正式求解 * * @param num * @param data * @return */ public static Set<Integer> getSingleKeyLock(int num, Set<Integer> data) { if (data == null) data = new HashSet<>(); if (isZhishu(num)) { data.add(num); } else { List<Integer> temp = zhengChu(num); for (Integer integer : temp) getSingleKeyLock(integer, data); } return data; } public static Set<Integer> getSingleKeyLock(int num) { return getSingleKeyLock(num, null); } /** * 求 1 到 n 所有质数 * * @param n * @return */ private static int[] _getPrimeNumber(int n) { int[] priArr = new int[n]; // 质数为大于1的自然数, 故i从2开始 for (int i = 2; i < n; i++) { // isPrime作为当前这个数是否为质数的标记位 boolean isPrime = true; for (int j = 2; j < i; j++) { if (i % j == 0) { isPrime = false; break; } } if (isPrime) priArr[i] = i; } return priArr; } /** * 求 1 到 n 所有质数 * * @param n * @return */ public static Integer[] getPrimeNumber(int n) { int[] arr = _getPrimeNumber(n); List<Integer> list = new ArrayList<>(); for (int i = 0; i < arr.length; i++) { if (arr[i] != 0) list.add(arr[i]); } return list.toArray(new Integer[list.size()]); } static int Rmax = 4; // 权限值的最大值 public static int getR(int Ki, int Lj) { int Rij = 0, Temp = Lj; while (true) { if (Rij == Rmax) break; if ((Temp % Ki) == 0) { // isInt() 判断是否整数 Rij++; Temp = Temp / Ki; } else break; } return Rij; } @Override public Long create(Map<String, Object> bean) { Integer[] ep = getExistingPrime(); int prime; if (ep == null || ep.length == 0) { prime = 2; } else prime = getNextPrime(ep); bean.put("accessKey", prime); return super.create(bean); } /** * 获取当前最大的质数 * * @return 当前最大的质数 */ public Integer[] getExistingPrime() { return dao.getExistingPrime(); } /** * 生成下一个质数 * * @param existingPrime 当前最大的质数 * @return */ public static int getNextPrime(Integer[] existingPrime) { int max = Collections.max(Arrays.asList(existingPrime)); Integer[] p = RoleUtil.getPrimeNumber(200); for (int i : p) { if (i > max) return i; } return 0; } }
单测:
package com.ajaxjs.user; import static com.ajaxjs.user.role.RoleUtil.getR; import static com.ajaxjs.user.role.RoleUtil.getSingleKeyLock; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import java.util.Arrays; import java.util.Set; import org.junit.Test; import com.ajaxjs.user.role.RoleService; import com.ajaxjs.user.role.RoleUtil; public class TestRole { @Test public void testGetGetExistingPrime() { Integer[] ep = new RoleService().getExistingPrime(); Arrays.toString(ep); } @Test public void testGetGetPrimeNumber() { assertEquals("[2, 3, 5, 7]", Arrays.toString(RoleUtil.getPrimeNumber(10))); } @Test public void testGetSingleKeyLock() { Set<Integer> ints = getSingleKeyLock(686070); assertEquals("[2, 3, 5, 7, 11]", ints.toString()); } @Test public void testGetR() { int r = getR(5, 686070); assertNotNull(r); System.out.println("::::::" + (15 & 7)); } }
很遗憾的说,推酷将在这个月底关闭。人生海海,几度秋凉,感谢那些有你的时光。
原文 https://blog.csdn.net/zhangxin09/article/details/107530676