基于SpringAOP实现数据权限控制
在此主要是实现对用户查询数据返回字段的控制。比如一个表格有A,B,C,D,E五列,用户U1只能查看A,B,C三列。
此文章讲述的内容并不能实现在查询时仅查询A,B,C三列,而是在查询后做过滤,将D,E两列的值置为空。
本文只启到抛砖引玉的作用,代码并没有完全实现。只写了核心部分。如果大家用到的话,还需要根据自己项目的权限体系完善。
准备工作
首先定义注解QueryMethod,用于标注方法是查询方法。
/**
* 标识此方法为查询方法,可能会受到数据权限控制,理论上所有查询方法都应该加上此注释
*
* @author Wang Chengwei
* @since 1.0.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface QueryMethod {
}
1
2
3
4
5
6
7
8
9
10
11
定义查询方法返回的结果
/**
* 支持过滤的结构,用于在AOP方法中对要显示的数据进行控制
*
* @author Wang Chengwei
* @since 1.0.0
*/
public class FilterableResult<T> implements Filterable<T>, MetaSetter {
@Getter
@Setter
private List<T> rows;
private List<SysDataResource> meta;
@Override
public void doFilter(Function<T, T> filterFunc) {
for (T row : rows) {
filterFunc.apply(row);
}
}
@Override
public void setMeta(List<SysDataResource> dataResources) {
this.meta = dataResources;
}
@Override
public List<SysDataResource> getMeta() {
return this.meta;
}
}
/**
* 支持过滤
*
* @author Wang Chengwei
* @since 1.0.0
*/
public interface Filterable<T> {
/**
* 遍历列表,执行过滤方法
* @param filterFunc 过滤方法
*/
void doFilter(Function<T, T> filterFunc);
}
/**
* 设置数据结构
*
* @author Wang Chengwei
* @since 1.0.0
*/
public interface MetaSetter {
/**
* 设置数据结构,用于前台展示
* @param dataResources 数据结构
*/
void setMeta(List<SysDataResource> dataResources);
/**
* 获取数据结构
* @return 数据结构
*/
List<SysDataResource> getMeta();
}
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
SysDataResource为数据资源项。
@Table(name = "tbsd_sys_data_resource")
public class SysDataResource {
/**
* 数据ID
*/
@Id
@Column(name = "data_id")
private String dataId;
/**
* 权限ID
*/
@Column(name = "authority_id")
private String authorityId;
/**
* 数据项名称
*/
@Column(name = "data_name")
private String dataName;
/**
* 数据标志符号
*/
@Column(name = "data_code")
private String dataCode;
/**
* 创建时间
*/
@Column(name = "create_time")
private Date createTime;
// 扩展字段
/**
* 是否允许访问
*/
@Column(name = "is_accessible")
private Boolean isAccessible;
}
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
用户数据资源权限说明
系统权限对应数据资源,权限中设置访问数据的业务方法。
authorityName: 用户查询
authorityMark: AUTH_USER_QUERY
classAndMethod: com.wawscm.shangde.module.security.service.impl.SysUserServiceImpl.findUser(int,int)
1
2
3
classAndMethod要明确到实现类,本文档中的方法不支持接口方法。
用户拥有此权限后就可以设置对应的数据资源访问权限。
资源名称 标识
用户ID userId
用户名 username
密码 password
用户姓名 name
用户的资源权限设置如下
资源名称 标识 isAccessible
用户ID userId true
用户名 username true
密码 password false
用户姓名 name false
SysUser bean代码如下
@Table(name = "tbsd_sys_user")
public class SysUser {
/**
* 用户ID
*/
@Id
@Column(name = "user_id")
private String userId;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 用户姓名
*/
private String name;
/**
* 手机号
*/
@Column(name = "phone_num")
private String phoneNum;
/**
* 用户状态(1-正常;2-冻结)
*/
@Column(name = "user_state")
private String userState;
/**
* 用户类型(1-系统管理员;2-分店管理员;3-便利店管理员;4-普通用户)
*/
@Column(name = "user_type")
private String userType;
/**
* 店铺ID(总部用户字段为空)
*/
@Column(name = "store_id")
private String storeId;
/**
* 最后一次登陆时间
*/
@Column(name = "last_login_time")
private Date lastLoginTime;
/**
* 创建时间
*/
@Column(name = "create_time")
private Date createTime;
}
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
拦截方法过滤数据
主要根据SysDataResource.isAccessible来判断是否有字段的访问权限,如果值为false则认为没有权限,其他字段不管,因为数据的权限控制,可能只是控制某几个字段,而不是全部。比如一些id类的字段。我们不希望在设置数据资源时还要设置表格中并不显示的字段。
核心代码如下。
/*
* Copyright © 2016-2018 WAWSCM Inc. All rights reserved.
*/
package com.wawscm.shangde.interceptor;
import com.wawscm.shangde.base.Filterable;
import com.wawscm.shangde.base.MetaSetter;
import com.wawscm.shangde.base.SystemSettings;
import com.wawscm.shangde.module.security.helper.UserAuthorityHelper;
import com.wawscm.shangde.module.security.model.SysDataResource;
import com.wawscm.shangde.utils.ShiroUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.beans.PropertyDescriptor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 数据权限拦截器
*
* @author Wang Chengwei
* @since 1.0.0
*/
@Component
@Aspect
public class DataResourceAuthorityInterceptor {
@Autowired
private UserAuthorityHelper userAuthorityHelper;
@Autowired
private SystemSettings systemSettings;
/**
* 切入点设置,拦截所有具有{@link com.wawscm.shangde.annotation.QueryMethod}注解的方法
*/
@Pointcut("@annotation(com.wawscm.shangde.annotation.QueryMethod)")
public void queryMethodPointcut() {
}
/**
* 环绕通知
* @param joinPoint ProceedingJoinPoint
* @return 方法返回的对象
* @throws Throwable 方法执行时抛出的异常,此处不做任何处理,直接抛出
*/
@Around(value = "queryMethodPointcut()")
public Object doInterceptor(ProceedingJoinPoint joinPoint) throws Throwable {
Object object = joinPoint.proceed();
String methodName = this.getMethodName(joinPoint);
if (object != null) {
if (object instanceof Filterable) {
this.doFilter((Filterable) object, methodName);
}
if (object instanceof MetaSetter) {
this.metaHandler((MetaSetter)object, methodName);
}
}
return object;
}
/**
* 执行过滤操作
* @param filterable 方法返回的对象
* @param methodName 拦截的方法名称
*/
private void doFilter(Filterable<?> filterable, String methodName) {
List<SysDataResource> resources = this.getDataResources(methodName);
// 如果
if (CollectionUtils.isEmpty(resources)) {
return;
}
filterable.doFilter(o -> {
Map<String, SysDataResource> dataColumnMap = new HashMap<>(resources.size());
for (SysDataResource column : resources) {
dataColumnMap.put(column.getDataCode(), column);
}
PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(o.getClass());
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
String name = propertyDescriptor.getName();
SysDataResource dataColumn = dataColumnMap.get(name);
if (dataColumn != null && !dataColumn.getIsAccessible()) {
try {
propertyDescriptor.getWriteMethod().invoke(o, new Object[] {null});
} catch (Exception ex) {
// skip
}
}
}
return o;
});
}
/**
* 设置数据结构
* @param metaSetter 方法返回的对象
* @param methodName 拦截的方法名称
*/
private void metaHandler(MetaSetter metaSetter, String methodName) {
List<SysDataResource> resources = this.getDataResources(methodName);
if (resources != null) {
metaSetter.setMeta(resources);
} else { // 如果没有设置数据资源,默认用户拥有访问全部资源的权限
List<SysDataResource> allResources = findAuthorityDataResource(methodName);
metaSetter.setMeta(allResources);
}
}
/**
* 根据方法名和用户ID获取用户的数据权限
* @param methodName 拦截的方法名称
* @return 用户的数据权限
*/
private List<SysDataResource> getDataResources(String methodName) {
String userId = ShiroUtil.getUserId();
return this.userAuthorityHelper.getDataResource(methodName, userId);
}
/**
* 获取此方法对应的所有数据资源项
* @param methodName 拦截的方法名称
* @return 用户的数据权限
*/
private List<SysDataResource> findAuthorityDataResource(String methodName) {
return null; // 此处代码省略
}
private String getMethodName(ProceedingJoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
// systemSettings.isSupportMethodParams()表示是否支持方法参数,默认支持。如果设置为不支持,则权限中的方法应设置为com.wawscm.shangde.module.security.service.impl.SysUserServiceImpl.findUser
if (systemSettings.isSupportMethodParams() && signature instanceof MethodSignature) {
MethodSignature methodSignature = (MethodSignature)signature;
StringBuilder sb = new StringBuilder();
sb.append(methodSignature.getDeclaringTypeName());
sb.append(".");
sb.append(methodSignature.getName());
sb.append("(");
Class<?>[] parametersTypes = methodSignature.getParameterTypes();
for (int i = 0; i < parametersTypes.length; i++) {
if (i > 0) {
sb.append(",");
}
Class<?> parametersType = parametersTypes[i];
sb.append(parametersType.getSimpleName());
}
sb.append(")");
return sb.toString();
} else {
StringBuilder sb = new StringBuilder();
sb.append(signature.getDeclaringTypeName());
sb.append(".");
sb.append(signature.getName());
return sb.toString();
}
}
}
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
UserAuthorityHelper代码如下,此处的数据均为模拟数据。正确的做法应该是从数据库或缓存中获取
/**
* 用户权限工具类
*
* @author Wang Chengwei
* @since 1.0.0
*/
@Component
public class UserAuthorityHelper {
public List<SysDataResource> getDataResource(String methodName, String userId) {
List<SysDataResource> resources = new ArrayList<>();
SysDataResource resource1 = new SysDataResource();
resource1.setDataCode("userId");
resource1.setDataName("用户ID");
resource1.setIsAccessible(true);
SysDataResource resource2 = new SysDataResource();
resource2.setDataCode("username");
resource2.setDataName("用户名");
resource2.setIsAccessible(true);
SysDataResource resource3 = new SysDataResource();
resource3.setDataCode("password");
resource3.setDataName("密码");
resource3.setIsAccessible(false);
SysDataResource resource4 = new SysDataResource();
resource4.setDataCode("name");
resource4.setDataName("用户姓名");
resource4.setIsAccessible(false);
resources.add(resource1);
resources.add(resource2);
resources.add(resource3);
resources.add(resource4);
return resources;
}
}
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
SysUserServiceImpl代码如下,此处的数据也是模拟数据
/**
* 用户业务
*
* @author Wang Chengwei
* @since 1.0.0
*/
@Service
public class SysUserServiceImpl implements SysUserService {
@Override
@QueryMethod
public FilterableResult<SysUser> findUser(int page, int pageNum) {
List<SysUser> users = new ArrayList<>();
users.add(mockUser());
users.add(mockUser());
users.add(mockUser());
users.add(mockUser());
users.add(mockUser());
users.add(mockUser());
users.add(mockUser());
users.add(mockUser());
users.add(mockUser());
users.add(mockUser());
System.out.println("返回的数据");
System.out.println(JsonKit.toJson(users));
return FilterableResult.build(users);
}
private SysUser mockUser() {
SysUser sysUser = new SysUser();
sysUser.setUserId(UUIDGenerator.genertate());
sysUser.setUsername(UUIDGenerator.genertate());
sysUser.setName(UUIDGenerator.genertate());
sysUser.setPassword(UUIDGenerator.genertate());
sysUser.setPhoneNum(UUIDGenerator.genertate());
sysUser.setCreateTime(new Date());
sysUser.setLastLoginTime(new Date());
return sysUser;
}
}
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
测试
public class SysUserServiceImplTest extends BaseSpringTestCase {
@Autowired
private SysUserService sysUserService;
@Test
public void findUser() {
FilterableResult<SysUser> users = this.sysUserService.findUser(1, 15);
System.out.println("过滤后的数据");
System.out.println(JsonKit.toJson(users));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
过滤前的数据为
{
"rows": [
{
"userId": "838563855e3e44489d6dc91c8a37031a",
"username": "b6f89f7ec27e434e92638a063b310a66",
"password": "0ec85df1f31f4d88b9efbb62c46863f9",
"name": "3cf146b6f13c46ef9372c19f734fa712",
"phoneNum": "e42d86e8212943a7926515cc5aaf0dab",
"lastLoginTime": "2018-01-05 18:47:52",
"createTime": "2018-01-05 18:47:52"
},
{
"userId": "8cfcd7ccaa3442edb8c4175e5e4e7e9e",
"username": "632f1491d576486bb936d7da8ddf1bf6",
"password": "acc506932c194adf963de57a3f651ac6",
"name": "dfa65420b26f4222abc3e4477ec0efc4",
"phoneNum": "619e24618a894368b3d3f4a242bc9a81",
"lastLoginTime": "2018-01-05 18:47:52",
"createTime": "2018-01-05 18:47:52"
}
......
]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
过滤后的数据为
{
"rows": [
{
"userId": "838563855e3e44489d6dc91c8a37031a",
"username": "b6f89f7ec27e434e92638a063b310a66",
"phoneNum": "e42d86e8212943a7926515cc5aaf0dab",
"lastLoginTime": "2018-01-05 18:47:52",
"createTime": "2018-01-05 18:47:52"
},
{
"userId": "8cfcd7ccaa3442edb8c4175e5e4e7e9e",
"username": "632f1491d576486bb936d7da8ddf1bf6",
"phoneNum": "619e24618a894368b3d3f4a242bc9a81",
"lastLoginTime": "2018-01-05 18:47:52",
"createTime": "2018-01-05 18:47:52"
}
......
],
"meta": [
{
"dataName": "用户ID",
"dataCode": "userId",
"isAccessible": true
},
{
"dataName": "用户名",
"dataCode": "username",
"isAccessible": true
},
{
"dataName": "密码",
"dataCode": "password",
"isAccessible": false
},
{
"dataName": "用户姓名",
"dataCode": "name",
"isAccessible": false
}
]
}
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
从结果上可以看出password,name这两个字段已经被过滤掉了,同时增加了meta数据结构内容。前台可以根据meta中返回的数据来创建表格,实现表格的动态显示。
原文:https://blog.youkuaiyun.com/jaune161/article/details/78984490?utm_source=copy
版权声明:本文为博主原创文章,转载请附上博文链接!