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<String, Object> 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> 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<String, Object> 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<String, Object> getParams(JoinPoint joinPoint) {
		Map<String, Object> 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;
	}
}

 

發佈了14 篇原創文章 · 獲贊 1 · 訪問量 5122
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章