Spring AOP和自定义注解实现数据权限

        在日常的开发中,数据权限都是重中之重,数据权限的管控可以分为两种,一种是:对查询参数进行过滤,还有一种是:对返回结果进行过滤,一般这两种方式是在程序中配合使用的。上面这两种数据权限的管控方式,可以通过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;
	}
}

 

你可能感兴趣的:(java,spring,SpringBoot)