基于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 {}

定义查询方法返回的结果

/*** 支持过滤的结构,用于在AOP方法中对要显示的数据进行控制** @author Wang Chengwei* @since 1.0.0*/
public class FilterableResult<T> implements Filterable<T>, MetaSetter {@Getter@Setterprivate List<T> rows;private List<SysDataResource> meta;@Overridepublic void doFilter(Function<T, T> filterFunc) {for (T row : rows) {filterFunc.apply(row);}}@Overridepublic void setMeta(List<SysDataResource> dataResources) {this.meta = dataResources;}@Overridepublic List<SysDataResource> getMeta() {return this.meta;}public static <T> FilterableResult<T> build(List<T> rows) {FilterableResult<T> result = new FilterableResult<>();result.setRows(rows);return result;}
}/*** 支持过滤** @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();
}

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;
}

用户数据资源权限说明

系统权限对应数据资源,权限中设置访问数据的业务方法。

authorityName: 用户查询
authorityMark: AUTH_USER_QUERY
classAndMethod: com.wawscm.shangde.module.security.service.impl.SysUserServiceImpl.findUser(int,int)

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;
}

拦截方法过滤数据

主要根据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 {@Autowiredprivate UserAuthorityHelper userAuthorityHelper;@Autowiredprivate 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.findUserif (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();}}
}

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;}
}

SysUserServiceImpl代码如下,此处的数据也是模拟数据

/*** 用户业务** @author Wang Chengwei* @since 1.0.0*/
@Service
public class SysUserServiceImpl implements SysUserService {@Override@QueryMethodpublic 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;}
}

测试

public class SysUserServiceImplTest extends BaseSpringTestCase {@Autowiredprivate SysUserService sysUserService;@Testpublic void findUser() {FilterableResult<SysUser> users = this.sysUserService.findUser(1, 15);System.out.println("过滤后的数据");System.out.println(JsonKit.toJson(users));}
}

过滤前的数据为

{"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"}......]
}

过滤后的数据为

{"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}]
}

从结果上可以看出password,name这两个字段已经被过滤掉了,同时增加了meta数据结构内容。前台可以根据meta中返回的数据来创建表格,实现表格的动态显示。


原创文章,转载请注明出处!

基于SpringAOP实现数据权限控制相关推荐

  1. java 数据 权限_Java如何利用Mybatis进行数据权限控制详解

    前言 权限控制主要分为两块,认证(Authentication)与授权(Authorization).认证之后确认了身份正确,业务系统就会进行授权,现在业界比较流行的模型就是RBAC(Role-Bas ...

  2. Java利用Mybatis进行数据权限控制

    权限控制主要分为两块,认证(Authentication)与授权(Authorization).认证之后确认了身份正确,业务系统就会进行授权,现在业界比较流行的模型就是RBAC(Role-Based ...

  3. 实现权限控制_SpringCloud 微服务实现数据权限控制

    前章讲了如何进行用户权限验证<SpringCloud下的用户鉴权方案>,它是微服务下统一资源访问权限的控制,就像一道墙保护着SpringCloud集群下的各个业务应用服务.而本章要讲的是权 ...

  4. 实现数据权限控制的一种方法

    在企业管理系统中,常常有这样的要求:  1. 用户一般只能查看自己部门的数据  2. 可以设置用户可以查看哪些部门的数据  这种权限的控制,一般称为数据权限,与之对应的功能权限,则是系统中哪些功能可以 ...

  5. 基于ruoyi的数据权限设置

    目录 一.代码修改 1.生成代码 2.代码修改 二.权限使用和测试 1.部门设置 三.测试 一.代码修改 基于ruoyi实现数据权限的话,首先需要一系列代码,既单表的增删改查 1.生成代码 用这个表来 ...

  6. SpringSecurity系列——其他的权限控制,基于access表达式的权限控制day6-2(源于官网5.7.2版本)

    SpringSecurity系列--其他的权限控制,基于access表达式的权限控制day6-2(源于官网5.7.2版本) 常见权限控制总表 基于access表达式的权限控制 实例1:改写hasRol ...

  7. 天鸟技术中台-建设过程-日常经验2:通用参数和数据权限控制

    技术中台,服务于 无数个 独立的项目. 每个项目,单独对应1个App和appId. 查询和创建等接口,必须提供appId,区分是哪个app的数据. 而技术中台的平台管理端,是管理所有的数据,appId ...

  8. 后台管理系统2——数据权限控制

    文章目录 1 数据权限控制 1.1数据库的修改 1.2 后端代码的修改 1.3 前端代码的修改 1.4 Aside组件的修改 1 数据权限控制 通过不同的身份进行实现不同的功能. 1.1数据库的修改 ...

  9. ASP.NET Core 实战:基于 Jwt Token 的权限控制全揭露

    一.前言 在涉及到后端项目的开发中,如何实现对于用户权限的管控是需要我们首先考虑的,在实际开发过程中,我们可能会运用一些已经成熟的解决方案帮助我们实现这一功能,而在 Grapefruit.VuCore ...

最新文章

  1. Spring Cloud Alibaba【Nacos 服务治理】 高可用保证:Nacos 如何有效构建注册中心集群
  2. java 同步块 抛出异常_不把 wait 放在同步块中,为啥这种情况不会抛出 IllegalMonitorStateException?...
  3. leetcode 970. 强整数(Powerful Integers)
  4. [vue] 组件中写name选项有什么作用?
  5. win10雷电3接口驱动_技嘉推出B550 主板首发雷电3接口:40Gbps速率、Intel主控
  6. 关于spring cloud的几个问题
  7. Java 算法 复数求和
  8. 怎样调整input框背景颜色_不用花钱,自己也能制作证件照,更换背景色、排版全搞定...
  9. 几个常用的视频分析工具
  10. EDA课程设计,可编程逻辑设计课程设计
  11. Simulink子系统
  12. android图片显示组件,Android可循环显示图像的Android Gallery组件用法实例
  13. R 多变量数据预处理_R语言数据可视化之数据分布图(直方图、密度曲线、箱线图、等高线、2D密度图)...
  14. (转)如何获得两个日期相减的天数?
  15. 数据分析01 - 规范化方法
  16. 风控模型中的KS-指标
  17. 加密流量分类-论文1:Deep Learning for Encrypted Traffic Classification: An Overview
  18. JQ如何获取原生的event对象
  19. 各类证件类型的格式校验
  20. python人狗大战游戏_day23 python学习 类 人狗大战

热门文章

  1. Unable to load project
  2. php添加网站ico图标,如何添加网站ICO图标,添加ico图标的方法
  3. 腾讯企业邮箱要实名认证吗?
  4. android 8.0 nexus7,Android 7.0即将推送 Nexus用户最迟8月22日更新
  5. [我可怜的3110c!nbsp;续]
  6. PASS JNCIE
  7. 苏州科技大学计算机考研题型,苏州科技大学考研专业目录
  8. GO语言实现简单梅克尔树根哈希值求解
  9. Scratch的基础使用
  10. excel技巧总结(不断更新)