SpringBoot项目使用切面编程实现数据权限管理

springBoot项目使用切面编程实现数据权限管理

  • 什么是数据权限管理
  • 如何实现数据权限管理

什么是数据权限管理

不同用户在某页面看到数据不一致,实现每个用户之间数据隔离的效果。
如以下场景:
● 页面期望展示当前登录人所在部门的数据。
● 页面期望展示当前登录人所在部门及下级部门的数据。
● 页面期望展示当前登录人创建的数据。

如何实现数据权限管理

接下来我们实现一个简单的数据权限控制,规则只包括自定义sql,目的是在需要的时候将自定义sql拼接到sql中,并将变量替换成对应的值。
1、首先确定用户-角色-菜单-数据权限的关系
菜单有多个数据权限
角色可以绑定多个菜单的,绑定菜单时可以绑定数据权限
用户与角色绑定。
SpringBoot项目使用切面编程实现数据权限管理_第1张图片
2、定义注解,在方法上使用注解标识当前接口对应的菜单,为了查询数据权限规则,同时该注解可以作为切面的切入点。

/**
 *  数据权限注解
 * @Author taoyan
 * @Date 2019年4月11日
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
public @interface PermissionData {

    /**
     * 配置菜单的组件路径,用于数据权限
     */
    String permissionId() default "";
}

3、定义数据权限规则信息对象

@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "菜单权限规则表对象", description = "菜单权限规则表")
public class SysPermissionDataRule extends BaseEntity {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "id", required = true)
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;

    @ApiModelProperty(value = "对应的菜单id", required = true)
    @NotNull(message = "对应的菜单id不能为空")
    private Long permissionId;

    @ApiModelProperty(value = "规则名称")
    private String ruleName;

    @ApiModelProperty(value = "规则值")
    private String ruleValue;

    @ApiModelProperty(value = "状态;1:有效,0:无效")
    private String status;
}

4、定义切面,将用户信息和数据权限规则缓存再request中。
切面:

package com.sinosoft.springbootplus.datapermission.aspect;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.sinosoft.springbootplus.datapermission.aspect.annotation.PermissionData;
import com.sinosoft.springbootplus.core.context.RequestContext;
import com.sinosoft.springbootplus.datapermission.handler.PermissionDataDepInfoHandler;
import com.sinosoft.springbootplus.datapermission.handler.PermissionDataInfoHandler;
import com.sinosoft.springbootplus.system.domain.entity.SysPermissionDataRule;
import com.sinosoft.springbootplus.system.domain.service.SysPermissionDataRuleDomain;
import com.sinosoft.springbootplus.system.util.DataAutorUtils;
import com.sinosoft.springbootplus.util.HttpServletRequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
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.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

/**
 * 数据权限切面处理类
 *  当被请求的方法有注解PermissionData时,会在往当前request中写入数据权限信息
 * @Date 2019年4月10日
 * @Version: 1.0
 * @author: jeecg-boot
 */
@Aspect
@Component
@Slf4j
public class PermissionDataAspect {
 @Pointcut("@annotation(com.sinosoft.springbootplus.datapermission.aspect.annotation.PermissionData)")
    public void pointCut() {

    }
    @Around("pointCut()")
    public Object arround(ProceedingJoinPoint point) throws  Throwable{
        HttpServletRequest request = HttpServletRequestUtil.getRequest();
        //将用户信息放在request请求中
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        PermissionData pd = method.getAnnotation(PermissionData.class);
        String permissionId = pd.permissionId();
       
        //根据userId和菜单id获取绑定的角色对应的数据权限
        List<SysPermissionDataRule> dataRules = sysPermissionDataRuleDomain.getPermissionDataRulesByCompentAndUserId(permissionId,loginUserId);
        if(dataRules != null && dataRules.size()>0){
            //将权限信息暂存在request中
            DataAutorUtils.installDataSearchConditon(request,dataRules);
        }
        //将用户信息存储在request中
        DataPermissionContext dataPermissionContext = new DataPermissionContext();
        //从handler中获取用户部门信息
        if(ObjectUtil.isNotEmpty(permissionDataDepInfoHandler)){
            Map<String, Object> userInfo = permissionDataDepInfoHandler.getUserInfo(request, loginUserId);
            dataPermissionContext.putAll(userInfo);
        }
        //从handler中获取用户其他信息
        if (CollUtil.isNotEmpty(permissionDataInfoHandlers)) {
            for (PermissionDataInfoHandler permissionDataInfoHandler:permissionDataInfoHandlers) {
                Map<String, Object> userInfo = permissionDataInfoHandler.getUserInfo(request, loginUserId);
                if (null != userInfo) {
                    dataPermissionContext.putAll(userInfo);
                }
            }
        }

        DataAutorUtils.installUserInfo(dataPermissionContext);
        return  point.proceed();
    }
}

切面中往request中放和取用户信息、数据权限的工具类

public class DataAutorUtils {
    public static final String MENU_DATA_AUTHOR_RULES = "MENU_DATA_AUTHOR_RULES";

    public static final String SYS_USER_INFO = "SYS_USER_INFO";
    /**
     * 往链接请求里面,传入数据查询条件
     */
    public static synchronized void installDataSearchConditon(HttpServletRequest request, List<SysPermissionDataRule> dataRules) {
        request.setAttribute(MENU_DATA_AUTHOR_RULES, dataRules);
    }

    /**
     * 获取请求对应的数据权限规则
     */
    @SuppressWarnings("unchecked")
    public static synchronized List<SysPermissionDataRule> loadDataSearchConditon() {
        return (List<SysPermissionDataRule>) HttpServletRequestUtil.getRequest().getAttribute(MENU_DATA_AUTHOR_RULES);

    }

    /**
     * 将用户信息存到request
     */
    public static synchronized void installUserInfo(DataPermissionContext dataPermissionContext) {
        HttpServletRequestUtil.getRequest().setAttribute(SYS_USER_INFO, dataPermissionContext);
    }

    /**
     * 从request获取用户信息
     */
    public static synchronized DataPermissionContext loadUserInfo() {
        return (DataPermissionContext) HttpServletRequestUtil.getRequest().getAttribute(SYS_USER_INFO);

    }
}

5、在需要拼数据权限的接口上增加注解,并初始化queryWrapper,将request中的参数传进去,包括了该菜单的数据权限和用户信息

SpringBoot项目使用切面编程实现数据权限管理_第2张图片

4、初始化queryWrapper将数据权限规则拼接到sql中,拼接时会将用户信息map中的信息替换成自定义sql #{ }中的值,因此放用户信息时map中的key与自定义sql中#{ }的值要对应。

/**
 * 查询生成器
 */
@Slf4j
public class QueryGenerator {
    public static final String SQL_RULES_COLUMN = "SQL_RULES_COLUMN";

    /**
     * 获取查询条件构造器QueryWrapper实例 通用查询条件已被封装完成
     * @param searchObj 查询实体
     * @param parameterMap request.getParameterMap()
     * @return QueryWrapper实例
     */
    public static <T> QueryWrapper<T> initQueryWrapper(T searchObj, Map<String, String[]> parameterMap){
        long start = System.currentTimeMillis();
        QueryWrapper<T> queryWrapper = new QueryWrapper<T>();
        installMplus(queryWrapper, searchObj, parameterMap);
        log.debug("---查询条件构造器初始化完成,耗时:"+(System.currentTimeMillis()-start)+"毫秒----");
        return queryWrapper;
    }

    /**
     * 组装Mybatis Plus 查询条件
     * 

使用此方法 需要有如下几点注意: *
1.使用QueryWrapper 而非LambdaQueryWrapper; *
2.实例化QueryWrapper时不可将实体传入参数 *
错误示例:如QueryWrapper queryWrapper = new QueryWrapper(jeecgDemo); *
正确示例:QueryWrapper queryWrapper = new QueryWrapper(); *
3.也可以不使用这个方法直接调用 {@link #initQueryWrapper}直接获取实例 */ private static void installMplus(QueryWrapper<?> queryWrapper,Object searchObj,Map<String, String[]> parameterMap) { Map<String,SysPermissionDataRule> ruleMap = getRuleMap(); //权限规则自定义SQL表达式 for (String c : ruleMap.keySet()) { if(c.startsWith(SQL_RULES_COLUMN)){ queryWrapper.and(i ->i.apply(getSqlRuleValue(ruleMap.get(c).getRuleValue()))); } } } /** * 获取请求对应的数据权限规则 */ public static Map<String, SysPermissionDataRule> getRuleMap() { Map<String, SysPermissionDataRule> ruleMap = new HashMap<>(5); List<SysPermissionDataRule> list = DataAutorUtils.loadDataSearchConditon(); if(list != null&&list.size()>0){ if(list.get(0)==null){ return ruleMap; } for (SysPermissionDataRule rule : list) { String column = SQL_RULES_COLUMN+rule.getId(); ruleMap.put(column, rule); } } return ruleMap; } public static String getSqlRuleValue(String sqlRule){ try { Set<String> varParams = getSqlRuleParams(sqlRule); for(String var:varParams){ String tempValue = converRuleValue(var); sqlRule = sqlRule.replace("#{"+var+"}",tempValue); } } catch (Exception e) { log.error(e.getMessage(), e); } return sqlRule; } /** * 获取sql中的#{key} 这个key组成的set */ public static Set<String> getSqlRuleParams(String sql) { if(StringUtils.isEmpty(sql)){ return null; } Set<String> varParams = new HashSet<String>(); String regex = "\\#\\{\\w+\\}"; Pattern p = Pattern.compile(regex); Matcher m = p.matcher(sql); while(m.find()){ String var = m.group(); varParams.add(var.substring(var.indexOf("{")+1,var.indexOf("}"))); } return varParams; } public static String converRuleValue(String ruleValue) { String value = getUserSystemData(ruleValue); return value!= null ? value : ruleValue; } /** * 从当前用户中获取变量 */ public static String getUserSystemData(String key) { DataPermissionContext dataPermissionContext = DataAutorUtils.loadUserInfo(); Object o = dataPermissionContext.get(key); if(o instanceof String){ return dataPermissionContext.get(key).toString(); } if(o instanceof List){ List<String> result = new ArrayList<>(); for (Object item : (List<?>) o){ result.add((String) item); } return "(" + String.join(",", result) + ")"; } return null; } }

你可能感兴趣的:(spring,boot,aop)