在日常的开发中,数据权限都是重中之重,数据权限的管控可以分为两种,一种是:对查询参数进行过滤,还有一种是:对返回结果进行过滤,一般这两种方式是在程序中配合使用的。上面这两种数据权限的管控方式,可以通过spring aop + 自定义注解来实现。
自定义参数过滤注解,要在注解中获取到方法上需要进行数据过滤的参数名。代码如下:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 权限验证参数注解
*
* @author hrc
* @date 2019年12月17日
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface VerifyParam {
String value() default "";
// 条线参数名(需要控制数据权限的维度参数名)
String lineParamName();
}
自定义返回结果过滤注解,要在注解中获取到数据的class对象和要进行过滤的字段名称。代码如下:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 权限过滤返回结果注解
*
* @author hrc
* @date 2019年12月17日
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface FilterResults {
String value() default "";
// 返回数据对于的class对象
Class> dataClass();
// 要过滤的字段名
String fieldName();
}
1、权限验证参数切面的代码如下:
/**
* 权限验证参数切面
* @param joinPoint
* @param verifyParam
*/
@Before("@annotation(com.cheng.springdemo.common.annotation.VerifyParam) "
+ "&& @annotation(verifyParam)")
public void authVerifyParam(JoinPoint joinPoint, VerifyParam verifyParam) {
// 获取用户及其对应的条线
User user = getUser();
String lineCode = user.getLineCode();
// 获取方法上的参数名及值
Map params = getParams(joinPoint);
/*
* 1、获取到注解上要验证的参数名
* 2、判断用户是否有该条线的权限
*/
String paramName = verifyParam.lineParamName();
if (paramName != null) {
String lineParamValue = params.get(paramName).toString();
if (!lineParamValue.contains(lineCode)) {
throw new RuntimeException("您无权限访问该条线数据");
}
}
}
在上面的代码中,@Before("@annotation(com.cheng.springdemo.common.annotation.VerifyParam) && @annotation(verifyParam)")是表示该切面是在有@VerifyParam注解并有该注解的实例的方法调用前执行。
2、权限过滤返回结果切面
/**
* 权限过滤返回结果切面
* @param dataList
* @param filterResults
* @throws Exception
*/
@AfterReturning(pointcut = "@annotation(com.cheng.springdemo.common.annotation.FilterResults) "
+ "&& @annotation(filterResults)", returning = "dataList")
public void authFilterResults(List> dataList, FilterResults filterResults) throws Exception {
if (dataList == null || dataList.isEmpty()) {
return;
}
// 获取用户及其对应的条线
User user = getUser();
String lineCode = user.getLineCode();
// 获取注解上的数据class对象
Class> clazz = filterResults.dataClass();
Field field = null;
/*
* 1、获取注解上要验证的字段名
* 2、获取字段名对应的字段对象
*/
String fieldName = filterResults.fieldName();
if (fieldName != null && "".equals(fieldName)) {
field = clazz.getDeclaredField(fieldName);
if (!field.isAccessible()) {
field.setAccessible(true);
}
}
在上面的代码中,@AfterReturning(pointcut = "@annotation(com.cheng.springdemo.common.annotation.FilterResults) && @annotation(filterResults)", returning = "dataList")是表示该切面是在有@ FilterResults注解并有该注解的实例的方法调用成功之后执行。
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
import com.cheng.springdemo.common.annotation.FilterResults;
import com.cheng.springdemo.common.annotation.VerifyParam;
import com.cheng.springdemo.dto.User;
/**
* 权限验证
*
* @author hrc
* @date 2019年12月17日
*/
@Aspect
@EnableAspectJAutoProxy
@Component
public class AuthValidateAspect {
/*
* 这可以存用户名,也可以直接存放用户信息,看需求。
* 建议在过滤器或拦截器中设置用户信息,并且在请求完之后,把里面额用户信息清空。
*/
public final static ThreadLocal USER_THREAD_LOCAL = new ThreadLocal<>();
/**
* 权限验证参数切面
* @param joinPoint
* @param verifyParam
*/
@Before("@annotation(com.cheng.springdemo.common.annotation.VerifyParam) "
+ "&& @annotation(verifyParam)")
public void authVerifyParam(JoinPoint joinPoint, VerifyParam verifyParam) {
// 获取用户及其对应的条线
User user = getUser();
String lineCode = user.getLineCode();
// 获取方法上的参数名及值
Map params = getParams(joinPoint);
/*
* 1、获取到注解上要验证的参数名
* 2、判断用户是否有该条线的权限
*/
String paramName = verifyParam.lineParamName();
if (paramName != null) {
String lineParamValue = params.get(paramName).toString();
if (!lineParamValue.contains(lineCode)) {
throw new RuntimeException("您无权限访问该条线数据");
}
}
}
/**
* 权限过滤返回结果切面
* @param dataList
* @param filterResults
* @throws Exception
*/
@AfterReturning(pointcut = "@annotation(com.cheng.springdemo.common.annotation.FilterResults) "
+ "&& @annotation(filterResults)", returning = "dataList")
public void authFilterResults(List> dataList, FilterResults filterResults) throws Exception {
if (dataList == null || dataList.isEmpty()) {
return;
}
// 获取用户及其对应的条线
User user = getUser();
String lineCode = user.getLineCode();
// 获取注解上的数据class对象
Class> clazz = filterResults.dataClass();
Field field = null;
/*
* 1、获取注解上要验证的字段名
* 2、获取字段名对应的字段对象
*/
String fieldName = filterResults.fieldName();
if (fieldName != null && "".equals(fieldName)) {
field = clazz.getDeclaredField(fieldName);
if (!field.isAccessible()) {
field.setAccessible(true);
}
}
/*
* 判断返回数据是否是在用户权限内,如果不是则删掉。
*/
for (int i = 0; i < dataList.size(); i++) {
Object data = dataList.get(i);
if (field != null) {
String fieldValue = field.get(data).toString();
if (fieldValue != null && !fieldValue.contains(lineCode)) {
dataList.remove(i--);
continue;
}
}
}
}
/**
* 获取当前线程的用户信息
* @return
* @throws Exception
*/
private User getUser() {
User user = USER_THREAD_LOCAL.get();
if (user == null) {
// 当前线程没有用户信息,抛出异常
throw new RuntimeException("未登录");
}
return user;
}
/**
* 获取方法上的参数名及对应的值
* 该方法需要JDK8以上的支持,参考:https://blog.csdn.net/aitangyong/article/details/54376991
* @param joinPoint
* @return
*/
private Map getParams(JoinPoint joinPoint) {
Map params = new HashMap<>();
Object[] args = joinPoint.getArgs();
String[] parameterNames = null;
if (args != null && args.length > 0) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = null;
if (signature instanceof MethodSignature) {
methodSignature = (MethodSignature) signature;
parameterNames = methodSignature.getParameterNames();
if (parameterNames != null && parameterNames.length > 0) {
for (int i = 0; i < parameterNames.length && i < args.length; i++) {
params.put(parameterNames[i], args[i]);
}
}
}
}
return params;
}
}