• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

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

武飞扬头像
华妃
帮助1

什么是数据权限管理

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

如何实现数据权限管理

接下来我们实现一个简单的数据权限控制,规则只包括自定义sql,目的是在需要的时候将自定义sql拼接到sql中,并将变量替换成对应的值。
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中的参数传进去,包括了该菜单的数据权限和用户信息

学新通

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 查询条件
     * <p>使用此方法 需要有如下几点注意:
     * <br>1.使用QueryWrapper 而非LambdaQueryWrapper;
     * <br>2.实例化QueryWrapper时不可将实体传入参数
     * <br>错误示例:如QueryWrapper<JeecgDemo> queryWrapper = new QueryWrapper<JeecgDemo>(jeecgDemo);
     * <br>正确示例:QueryWrapper<JeecgDemo> queryWrapper = new QueryWrapper<JeecgDemo>();
     * <br>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;
    }

}

学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgaachb
系列文章
更多 icon
同类精品
更多 icon
继续加载