learn and grow up

自己写基于注解的参数级统一数据权限组件

字数统计: 69阅读时长: 1 min
2020/06/20 Share

写在前面

做了这么多准备工作,终于可以写完之前构思的数据权限组件,接下来分几篇文章来记录下这个组件的功能和结构

结构

结构图片太大,请点击一下链接下载

简要UML图

组件特点

功能权限:比较简单,不再赘述

数据权限:切面处理实现为:com.iiap.eng.aop.DataAuthAspect,基于参数级别的拦截,对controller方法参数注解,AOP根据注解来解析参数,再依据权限模型缓存判断是否超限,特点如下

  • 基于参数级别的拦截
  • 方便快速地接入
  • 对业务源码没有侵入和污染
    • 接入过程如下:
      • 实现com.iiap.interceptor.dataauth.DataAuthAbstractHandle,根据业务主键id(也就是第一个参数数组,支持多个)来获取该业务数据所属的数据权限,可参考已实现的样例 如:com.iiap.eng.aop.dataAuth.SpaceDataAuthHandle
      • 在需要拦截的controller方法的参数上增加注解com.iiap.interceptor.dataauth.DataAuthAnnotation,
  • 支持通配符白名单配置
  • 权限数据源缓存至redis
  • 支持参数的多fieldName、多拦截类按顺序拦截
  • 支持拦截器内查询数据缓存并后续在业务层使用
  • 拦截配置启动缓存至服务器内存中,可快速匹配和获取拦截配置

代码片段

废话不多说,直接上代码片段

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
//数据权限配置annotation+数据权限的拦截处理抽象类
/**
* 统一权限拦截处理父类
* @author yyt
*/
public interface AuthJudge {
//默认角色权限集合
//运营端
public static final List<Integer> operateCodes=new ArrayList(){{
this.add(SysRoleConstant.SysRoleEnum.COMPANY_SUPER_MANAGER.getCode());
this.add(SysRoleConstant.SysRoleEnum.SCHEMA_OPERATOR_USER.getCode());
this.add(SysRoleConstant.SysRoleEnum.SCHEMA_OPERATOR_MANAGER.getCode());

}};

//数据权限注解使用,注解只能是基础类型
public static final String[] operateCodesArray={SysRoleConstant.COMPANY_SUPER_MANAGER_CODE+"", SysRoleConstant.SCHEMA_OPERATOR_USER_CODE+"",SysRoleConstant.SCHEMA_OPERATOR_MANAGER_CODE+""};
public static final String[] implCodesArray={SysRoleConstant.COMPANY_SUPER_MANAGER_CODE+"", SysRoleConstant.SCHEMA_IMP_MANAGER_CODE+"",SysRoleConstant.SCHEMA_IMP_USER_CODE+""};

//实施段
public static final List<Integer> implCodes=new ArrayList(){{
this.add(SysRoleConstant.SysRoleEnum.COMPANY_SUPER_MANAGER.getCode());
this.add(SysRoleConstant.SysRoleEnum.SCHEMA_IMP_MANAGER.getCode());
this.add(SysRoleConstant.SysRoleEnum.SCHEMA_IMP_USER.getCode());
}};


/**
* 权限判断
* @param request
* @param response
* @param handler
* @param userProjectScheams
* @return
*/
default Boolean checkAuthentication(HttpServletRequest request, HttpServletResponse response,
Object handler, UserProjectScheams userProjectScheams){
if(userProjectScheams==null || CollectionUtils.isEmpty(userProjectScheams.getProjectUsers())){
getLogger().error("projectUsers is null:{}",userProjectScheams);
return false;
}

String projectId=getProjectIdByRequest(request);
String schemaId=getSchemaIdByRequest(request);
//哪个不为空,判断哪个
if(StringUtils.isNotBlank(projectId) && StringUtils.isNotBlank(schemaId)){
//项目ID和方案id均不为空,一起判断
return checkByUserProjectSchemas(projectId,schemaId,userProjectScheams);
}else if(StringUtils.isNotBlank(projectId)){
return checkByUserProjectSchemas(projectId,userProjectScheams);
}else{
return checkByUserProjectSchemas(userProjectScheams);
}
};

/**
* 获取需要权限的codes
* @return
*/
@NotNull
List<Integer> getAuthSysRoleCodes();

Logger getLogger();

/**
*....省掉其他无用代码
*/


}

/**
* 数据权限注解类
* @author yyt
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataAuthAnnotation {
//

/**
* 需要判断的id的fieldName集合,多个的话循环获取然后封装为Set后传给业务处理类获取业务属性
* 支持使用'.'来多层级获取属性,层级的root为参数自己
* 如果是GET请求,多个参数需要命名不同,所以需要写为 _this_/d (正则),如 _this_1,_this_2
*/
String[] idKeys();

//获取项目id与方案id的业务处理类,必填,
//可多个,多个处理类顺序由处理类的getOrder的返回int决定
Class<? extends DataAuthAbstractHandle>[] handleCls();


//需要拥有的角色code集合,必填
int[] roleCodes();

//其他参数,比如标示哪个模块,以便authHandle的业务代码可以复用,aop不做处理,会直接传给指定handle,自己在业务层做处理
String[] otherParams() default {};



//获取主键的方法,备用方法,暂无实际意义
String method() default "";

//用来判断是否需要是否发布校验,
//PUBLISH_CHECK:需要发布校验
String type() default "";
public static final String PUBLISH_CHECK="1";

public static final String THIS_KEY_REGEX="^_this_\\d*$";
public static final String THIS_KEY="_this_";

}

/**
* 数据权限逻辑处理抽象类
* 主要实现:数据权限校验整体流程把控
*
* @author yyt
* @date 2020-05-27 19:53:28
*/
public abstract class DataAuthAbstractHandle implements AuthJudge {
@Autowired
private ProjectUserCache projectUserCache;

@Autowired
private ProjectManager projectManager;

/**
* 数据权限基本判断domian
*/
@Data
public static class DataAuthDomain {
private String projectId="";
private String schemaId="";

@Override
public boolean equals(Object newV) {
if (newV == null) {
return false;
}
if (newV.getClass() == this.getClass()) {
DataAuthDomain newV2 = (DataAuthDomain) newV;
return StringUtils.equals(newV2.getProjectId(), projectId) &&
StringUtils.equals(newV2.getSchemaId(), schemaId);
}
return false;
}
@Override
public int hashCode(){
int result = 17;
result = result*31+projectId==null?0:projectId.hashCode();
result = result*31+schemaId==null?0:schemaId.hashCode();
return result;
}
//private List AuthData;
}

/**
* 根据业务id查询所有权限的domain集合
* @param ids,key filed->value id集合
* @param params 自定义参数
* @param fieldName 变量名称
* @return
*/
protected abstract Set<DataAuthDomain> getAuthDomain(Set<String> ids, String[] params,String fieldName);

/**
* 1、判断user是否用这些id的权限
* 2、判断发布权限
*
* @param userId
* @param ids
* @return
*/
public Boolean checkDataAuth(@NotNull DataAuthAnnotation dataAuthAnnotation, @NotNull String userId, @NotNull Map<String,Set<String>> ids) {
if (MapUtils.isEmpty(ids)) {
return true;
}

int[] roleCodes = dataAuthAnnotation.roleCodes();
String[] params = dataAuthAnnotation.otherParams();
List<Integer> roleList = Arrays.stream(roleCodes).boxed().collect(Collectors.toList());
for(Map.Entry<String, Set<String>> entry:ids.entrySet()) {
String fieldName=entry.getKey();
Set<String> vids=entry.getValue();
if(CollectionUtils.isEmpty(vids)){
continue;
}
Set<DataAuthDomain> dataAuthDomains = getAuthDomain(vids, params,fieldName);
if(CollectionUtils.isEmpty(dataAuthDomains)){
continue;
}
for (DataAuthDomain dataAuthDomain : dataAuthDomains) {
String projectId = dataAuthDomain.getProjectId();
String schemaId = dataAuthDomain.getSchemaId();
//进行权限校验,失败则立即返回
UserProjectScheams userProjectScheams = projectUserCache.getHashValue(userId);
if (!checkByUserProjectSchemas(roleList, projectId, schemaId, userProjectScheams)) {
return false;
}
//正在发布校验
//TODO 改为从缓存如redis取数判读
if (DataAuthAnnotation.PUBLISH_CHECK.equals(dataAuthAnnotation.type())) {
if (projectManager.hasSchemaInPublish(projectId)) {
getLogger().warn("ProjectId has schema ing publish, projectId:{}", projectId);
throw new CustomBusinessException(ErrorCodeUtils.PermissionErrorCode.SCHEMA_IN_PUBLISH);
}
}
}
}


return true;
}

/**
* 获取aop中获取的业务实体
*
*/
@Nullable
public static <T> T findInRequestAttributes(String fieldName){
Object o= AuthUtils.findInRequestAttributes(fieldName);
if(o==null){
return null;
}
return (T)o;
}

//排序字段,默认排到最后
public int getOrder(){
return 2<<16;
}
}
CATALOG
  1. 1. 写在前面
  2. 2. 结构
  3. 3. 组件特点
  4. 4. 代码片段