今天和一个网友争论了权限的问题,我感觉有必要分享下我的研究心得.
权限 无非是 角色 人员 资源 的配比关系
权限的操作无非是 增删改查,导入,导出 的操作,以及针对数据集的操作
业务结构无非是 公司,部门,用户

在rest时代,权限无非是 对一个URL是否有权限操作! 权限的本质就是URL的访问控制!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
-- ----------------------------
-- Table structure for `users` 
-- 都是常用的字段
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` varchar(40) NOT NULL,
  `name` varchar(50) DEFAULT NULL,
  `userCode` varchar(50) NOT NULL,
  `password` varchar(50) NOT NULL,
  `email` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

---角色表
---角色是继承关系
---level,roleCode为表示层级关系,部门表类似.主要用于快速查询,抛弃递归函数.
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `id` varchar(50) NOT NULL DEFAULT '',
  `pid` varchar(50) NOT NULL DEFAULT '',--父角色ID
  `roleName` varchar(200) NOT NULL,
  `level` int NOT NULL DEFAULT 0,--角色处的层级,根角色为1,它的子角色为2 孙角色为3
  `roleCode` varchar(200) NOT NULL,--根角色001 他的子角色为001001,001002...孙角色为001001001,001002001....依次类推.
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for `model`
-- ----------------------------
DROP TABLE IF EXISTS `model`;
CREATE TABLE `model` (
  `id` varchar(50) NOT NULL,
  `modelName` varchar(200) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

----
-- ----------------------------
-- Table structure for `re_user_role`用户角色中间表
-- ----------------------------
DROP TABLE IF EXISTS `re_user_role`;
CREATE TABLE `re_user_role` (
  `id` varchar(50) NOT NULL,
  `userId` varchar(50) NOT NULL,
  `roleId` varchar(50) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--角色资源中间表
DROP TABLE IF EXISTS `re_role_model`;
CREATE TABLE `re_role_model` (
  `id` varchar(50) NOT NULL,
  `roleId` varchar(50) NOT NULL,
 -- `startDate` datetime,
 -- `endDate` datetime,--表示权限的生存周期,用于领导临时分配权限,这里简化暂不讨论
 -- allunit int,--主要用于判断是否级联下级部门,这里不讨论
  `pageurl` varchar(1000) NOT NULL,
  `bool` int(11) NOT NULL DEFAULT '1',--表示真假,0为假,1为真
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

基本表结构如此,其他例如 unit 什么的这里就不写了说下场景,借用网友的一张图片啊
多公司权限模型

这里要重点说下 re_user_model 中间表的pageurl 和bool字段
pageurl是一个正则表达式,用于匹配用户访问的URL . bool表示真假,前提是pagurl必须匹配.
pageurl 和model表也没有直接关系了,只是一个正则表达式而已.

如果pagurl匹配时, bool=0表示不可访问,bool=1表示可以访问此url.例如 角色superuser 能够访问除了/admin/开头之外的所有url.就可以在数据库插入两条数据

如果pagurl匹配时, bool=0表示不可访问,bool=1表示可以访问此url.例如 角色superuser 能够访问除了/admin/开头之外的所有url.就可以在数据库插入两条数据

1
2
INSERT INTO `re_role_model` VALUES ('b1156249-9a8c-4f71-99ca-f56864401c41', 'superuser', '(.*)', '1');
INSERT INTO `re_role_model` VALUES ('fa1f2c33-b5b8-4134-ae50-53334de61fc3', 'superuser', '.*/admin/.*', '0');

根据 bool字段正序查询

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
String model_sql="SELECT re.* FROM re_role_model as re,re_user_role as rerole  
where 
re.roleId=rerole.roleId and rerole.userId=:userId order by re.bool asc";

//对比url是否匹配
boolean flag=false;
for(Re_Role_Model m:models){
String pageurl=m.getPageurl();
Integer bool=m.getBool();
Pattern pattern = Pattern.compile(pageurl);
Matcher matcher = pattern.matcher(requestURL);//请求的url是否具有权限
boolean f=matcher.matches();
if(f&&(bool==0)){//如果没有权限,跳出.
flag=false;
break;
}

if(f&&(bool==1)){
flag=true;
break;
}
}
return flag;

这是整个控制的核心 访问的url是根据正则匹配的!! 通过rest 将数据权限转变成对URL的访问控制

系统依赖rest实现权限的控制

约定url如下 : /{model}/{operation}/{公司ID}/ 为标准开头

操作约定为 save/delete/update/get/import/export

我认为 公司拥有单独的组织结构,可以认为是根,所以单独列出.当然也可以根据业务需要去掉{公司ID}

例如 /user/update/abc/unit789/123 (/user/update/{公司ID}/{部门ID}/{userID})意义为 修改 abc公司下部门为unit789 id为123的员工信息

所有的角色都是从根角色派生而来(可以根据业务实际需要决定是否角色继承),根角色为 admin(超级管理员) 其他的角色都是依次派生 就和主管分配权限是一致的. 例如 superuser 派生自admin

1
2
insert into role values('admin','','超级管理员','1','001')
insert into role values('superuser','admin','超级用户','2','001001')

主管操作是一样的,这个其实用户前台操作比较复杂,后台理顺关系还是比较简单的.每个用户都可以从自己的权限范围内分给其他用户(其实也是角色,只是后台默认生成了)

关于re_role_model 中的 allunit 主要是用来判断是否包含子部门

例如 admin给 superuser分配了.*/user/get/gs123/unitabc/.* (/user/get/{公司ID}/{部门ID}) 用来判断 superuser是否能够查询 unitabc下的所有子部门 如果unitabc下的部门较少 可以用多条记录代替 例如

..*/user/get/gs123/unitabc1/.* .*/user/get/gs123/unitabc2/.* 如果子部门较多 可以启用 allunit 字段,根据实际情况自行判断吧

pageurl正则表达式,反转获得实际权限数据 可 以扩展实现数据库正则函数,在sql中直接对比.

示例

1
select u.* from unit as u ,re_role_model as re where regfun('/update/'+u.id+'/',re.pageurl,re.bool) and re.roleid='admin'

关于大家关心的sql注入我这里说下

1.会有过滤器过滤非法的url字符

2.如果你伪造了url 可以通过我的权限过滤器,但是却无法找到controller,404.例如 我的controller 接收路径为 /usr/update/abc 你给我发送了 /user/update/abc/d/c controller无法相应此请求.

3.regfun这个函数的参数不是从前台获取的,而是根据业务逻辑后台拼装的!

————————————-权限系统的思想基本介绍完毕了.——————————-

来个实例

添加管理员 admin_add 有全局添加权限 .*/save/.* bool 为1.

当然也可以具体到某个公司,某个部门,某个人.也就是数据集权限

因为 有 bool 字段做判断. 可以很容易做出交叉权限,例如上例的superuser 除了/admin/其他的都有权限访问.

save/delete/update/get/import/export 其实代表了操作 可以根据需要添加角色的权限.角色是继承的,所以用户可以再自己的权限范围内 自由分配给其他用户.

当然有些也是可以系统定义的 例如

.*/user/delete/admin.* bool 为0 roleid为 * 任何角色都没有权限删除超级管理员的权限. 这些都是系统后台定义的.

最小权限.例如 普通用户 abc只有访问自己信息的权限

pageurl为: */user/update/abc.* (当然,这里可以使用二次解析的通配符,暂不讨论)

如果用户abc向非法修改 用户f的信息 那么访问的url是 */user/update/f.* 数据库没有匹配的url,没有权限访问