已经 SpringSecurity 在几个项目中 实现权限模块,对于数据库,也是思考了不少,从Mysql 到 mongodb 都不是特别满意,
在Mysql中,如果权限相对简单,那么还能接受,如果稍微复杂一点,那么就有点恶心了.
在最近一个项目中,使用mongodb 做多租户的权限,实现起来简单明了了很多,关系也没有那么绕,但是毕竟非关系型数据库,没有级联操作,修改删除,可能会留下一些脏数据,
虽然Spring Data Mongodb 有对象持久化的监听事件,但是依然需要手动编写一些处理过期,以及脏数据的代码.
最近发现有个东西叫做Neo4j,好说了,这个特别关系的数据库,第一个想法,就是很适合做这种关系复杂的权限模块.
这里模拟一个基于多租户的权限设计
1:租户依赖系统权限,根据租户付费套餐不一,拥有不一样的权限,但不可越过系统权限边界(废话)
2:租户可以创建角色(角色不可以越过租户权限的边界)
3:租户创建的用户,可以有多个角色(权限基于租户的权限,多角色叠加)
首先用kubernetes 启动一个neo4j
neo4j.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: neo4j
namespace: k8s-springcloud
spec:
replicas: 1
selector:
matchLabels:
app: neo4j
template:
metadata:
labels:
app: neo4j
spec:
nodeName: k8s-node-0
terminationGracePeriodSeconds: 60
hostNetwork: true
containers:
- name: neo4j
image: 192.168.91.137:5000/neo4j
volumeMounts:
- name: data
mountPath: /data
- name: conf
mountPath: /var/lib/neo4j/conf
volumes:
- name: data
hostPath:
path: /mnt/gv0/k8s-springcloud/neo4j/data
- name: conf
hostPath:
path: /mnt/gv0/k8s-springcloud/neo4j/conf
---
apiVersion: v1
kind: Service
metadata:
name: neo4j
namespace: k8s-springcloud
labels:
app: neo4j
spec:
type: NodePort
ports:
- name: api
port: 7687
nodePort: 7687
targetPort: 7687
- name: web
port: 7474
nodePort: 7474
targetPort: 7474
kubectl create -f neo4j.yaml
首先看看系统总权限(假设系统有两个模块:订单(增删改查),库存(增删改查))
代码:
AclTenantModuleRelation sysModuleRelation = new AclTenantModuleRelation(); AclModule orderModule = new AclModule(); orderModule.setCode("AUTH_ORDER"); orderModule.setName("订单管理"); AclMethod orderQuery = new AclMethod("AUTH_ORDER_QUERY", "查询订单"); orderQuery.setAclModule(orderModule); AclMethod orderDelete = new AclMethod("AUTH_ORDER_DELETE", "删除订单"); orderDelete.setAclModule(orderModule); AclMethod orderEdit = new AclMethod("AUTH_ORDER_EDIT", "编辑订单"); orderEdit.setAclModule(orderModule); AclMethod orderAdd = new AclMethod("AUTH_ORDER_ADD", "新增订单"); orderAdd.setAclModule(orderModule); orderModule.setAclMethods(new HashSet<>(Arrays.asList(orderAdd, orderDelete, orderEdit, orderQuery))); aclMethodService.saveAll(orderModule.getAclMethods()); aclModuleService.save(orderModule); sysModuleRelation.setTenantId(TenantInfo.SYSTEM_TENANT_ID); AclModule stockModule = new AclModule(); stockModule.setCode("AUTH_STOCK"); stockModule.setName("库存管理"); AclMethod stockQuery = new AclMethod("AUTH_STOCK_QUERY", "查询库存"); stockQuery.setAclModule(stockModule); AclMethod stockDelete = new AclMethod("AUTH_STOCK_DELETE", "删除库存"); stockDelete.setAclModule(stockModule); AclMethod stockEdit = new AclMethod("AUTH_STOCK_EDIT", "编辑库存"); stockEdit.setAclModule(stockModule); AclMethod stockAdd = new AclMethod("AUTH_STOCK_ADD", "新增库存"); stockAdd.setAclModule(stockModule); stockModule.setAclMethods(new HashSet<>(Arrays.asList(stockAdd, stockDelete, stockEdit, stockQuery))); aclMethodService.saveAll(stockModule.getAclMethods()); aclModuleService.save(stockModule); sysModuleRelation.setAclModules(new HashSet<>(Arrays.asList(orderModule,stockModule))); sysModuleRelation.setAclMethods(new HashSet<>(Arrays.asList(orderAdd, orderDelete, orderEdit, orderQuery,stockAdd, stockDelete, stockEdit, stockQuery)));
来设置一个租户,给租户分配一些权限
/** * User: laizhenwei * Date: 2018-03-25 Time: 15:09 */ @Test public void initTenantModule(){ AclModule aclModule = aclModuleService.findTop1ByCode("AUTH_STOCK"); TenantInfo tenantInfo = new TenantInfo("租户1"); tenantInfoService.saveAndFlush(tenantInfo); Iterator<AclMethod> aclMethodIterator = aclModule.getAclMethods().iterator(); AclTenantModuleRelation aclTenantModuleRelation = new AclTenantModuleRelation(tenantInfo.getId(),new HashSet<>(Arrays.asList(aclModule)),new HashSet<>(Arrays.asList(aclMethodIterator.next(),aclMethodIterator.next()))); aclTenantModuleRelationService.save(aclTenantModuleRelation); }
CQL
match (tenantModule:AclTenantModuleRelation)-[:HAS_OF]->(atMethod:AclMethod) where tenantModule.tenantId = '40288183625d600c01625d6032fa0000' match (atMethod)-[:DEPEND_OF]->(module:AclModule) return atMethod,module
模拟租户创建一个角色
/** * User: laizhenwei * Date: 2018-03-25 Time: 15:09 */ @Test public void addRole(){ Optional<AclTenantModuleRelation> aclTenantModuleRelationOptional = aclTenantModuleRelationService.findById(212l); aclTenantModuleRelationOptional.ifPresent(aclTenantModuleRelation -> { AclRole aclRole = new AclRole(); aclRole.setTenantId("40288183625d600c01625d6032fa0000"); aclRole.setCode("ROLE_NORMAL"); aclRole.setName("普通用户"); aclRole.setAclTenantModuleRelation(aclTenantModuleRelation); Iterator<AclModule> aclModuleIterator = aclTenantModuleRelation.getAclModules().iterator(); AclModule aclModule = aclModuleIterator.next(); Iterator<AclMethod> aclMethodIterator = aclTenantModuleRelation.getAclMethods().iterator(); aclRole.setAclModules(new HashSet<>(Arrays.asList(aclModule))); aclRole.setAclMethods(new HashSet<>(Arrays.asList(aclMethodIterator.next()))); aclRoleService.save(aclRole); }); }
CQL
match (t:AclTenantModuleRelation)-[:HAS_OF]->(method:AclMethod) match(r:AclRole)-[:HAS_OF]->(rmethod:AclMethod) where r.tenantId='40288183625d600c01625d6032fa0000' and r.code='ROLE_NORMAL' and method=rmethod match(method)-[:DEPEND_OF]->(module:AclModule) return method,module
结果(左),点击展开关系(右)
用户与角色,不再做演示.
想想SpringSecurity 的角色继承的配置格式为 role1>role2>role3 这种方式,用neo4j,是否很好实现动态角色继承?