基于SpringAOP实现数据权限控制
Posted 大扑棱蛾子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于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
定义查询方法返回的结果
/**
* 支持过滤的结构,用于在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;
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
@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();
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
@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()以上是关于基于SpringAOP实现数据权限控制的主要内容,如果未能解决你的问题,请参考以下文章