已經 SpringSecurity 在幾個項目中 實現權限模塊,對於數據庫,也是思考了很多,從Mysql 到 mongodb 都不是特別滿意,node
在Mysql中,若是權限相對簡單,那麼還能接受,若是稍微複雜一點,那麼就有點噁心了.web
在最近一個項目中,使用mongodb 作多租戶的權限,實現起來簡單明瞭了不少,關係也沒有那麼繞,可是畢竟非關係型數據庫,沒有級聯操做,修改刪除,可能會留下一些髒數據,spring
雖然Spring Data Mongodb 有對象持久化的監聽事件,可是依然須要手動編寫一些處理過時,以及髒數據的代碼.sql
最近發現有個東西叫作Neo4j,好說了,這個特別關係的數據庫,第一個想法,就是很適合作這種關係複雜的權限模塊.mongodb
這裏模擬一個基於多租戶的權限設計數據庫
1:租戶依賴系統權限,根據租戶付費套餐不一,擁有不同的權限,但不可越過系統權限邊界(廢話)api
2:租戶能夠建立角色(角色不能夠越過租戶權限的邊界)app
3:租戶建立的用戶,能夠有多個角色(權限基於租戶的權限,多角色疊加)spa
首先用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,是否很好實現動態角色繼承?