在日常的開發中,數據權限都是重中之重,數據權限的管控可以分爲兩種,一種是:對查詢參數進行過濾,還有一種是:對返回結果進行過濾,一般這兩種方式是在程序中配合使用的。上面這兩種數據權限的管控方式,可以通過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;
}
}