已經 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,是否很好實現動態角色繼承?
