基于SpringAOP实现数据权限控制

基于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 
版权声明:本文为博主原创文章,转载请附上博文链接!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值