Java若依框架权限隔离微调,基于groovy动态脚本实现动态隔离

文章目录

前言

由于若依的权限隔离过于简单,满足不了公司现有的需求,所以作者对现有的需求进行更改,这里作者采用了Groovy脚本对方法进行动态设置,然后传入当前用户信息和所有用户部门的信息,在脚本中进行计算并采用与若依一样的设计通过切面的方式将sql注入到目标sql中,这里将实现方式进行记录


一、前置条件

若依框架以及相关环境,Groovy 4.0.9

        <dependency>
            <groupId>org.apache.groovy</groupId>
            <artifactId>groovy</artifactId>
            <version>4.0.9</version>
        </dependency>

二、使用步骤

1.配置以及注解扫描

首先加入配置类在项目启动时对@DataScopePlus进行扫描

@Configuration
public class DataScopeConfig implements ApplicationContextAware, ApplicationRunner {

    private ApplicationContext context;

    /**
     * 在新项目启动时获取被标注的@DataScopePlus标注的方法,如何将方法缓存以供用户设置
     */
    public void doHandleDataScopeClass() {
        Map<String,String> container = MapUtil.newHashMap();
        Map<String, Object> serviceMap = context.getBeansWithAnnotation(Service.class);
        Collection<Object> values = serviceMap.values();
        for (Object value : values) {
            if (AopUtils.isCglibProxy(value)) {
                Class<?> clazz = AopUtils.getTargetClass(value);
                for (Method method : clazz.getMethods()) {
                    if (method.isAnnotationPresent(DataScopePlus.class)) {
                        String clazzName = clazz.getName();
                        String methodName = method.getName();
                        container.put(clazzName, methodName);
                    }
                }
            }

        }
        //放入缓存或者放入数据库进行持久化,以供配置选择
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        this.doHandleDataScopeClass();
    }
}

/**
 * 数据权限过滤注解
 *
 * @author 玄夜喀拉
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScopePlus {
}

2.实体设计与缓存

将被标记@DataScope的方法进行缓存,这里是实体Model信息

@Data
public class DataScopeCacheModel implements Serializable {
    private static final long serialVersionUID=1L;

    /**
     * groovy脚本的id
     */
    private String scriptId;
    /**
     * 特殊权限(拥于特殊权限的角色不进行权限隔离)
     */
    private Set<String> spelRole;

    /**
     * 类名
     */
    private String clazz;
    /**
     * 方法名
     */
    private String method;

    /**
     * 表信息
     */
    private Queue<DataScopeTableInfo> info;

    /**
     * 权限隔离表信息类
     */
    static class DataScopeTableInfo{

        /**
         * 表别名
         */
        private String tableAlias;
        /**
         * 字段别名
         */
        private String fieldAlias;

        /**
         * 连接符  and 或者 or... 使用or可能会导致索引失效,这里要结合实际业务来实现
         */
        private String connector;
    }
}

3.切面的改造

这里对原有的若依切面进行了改造,因为若依原有的数据隔离方式过少,不符合公司现有的需求,这里改为了通过脚本的方式调用,如何发生异常了为了防止数据泄露直接拼接1!=1进行数据的隔离

    @Before("@annotation(controllerDataScope)")
    public void doBefore(JoinPoint point, DataScopePlus controllerDataScope) {
        try {
            clearDataScope(point);
            Signature signature = point.getSignature();
            String className = signature.getDeclaringTypeName();
            String methodName = signature.getName();
            //通过className和MethodName获取缓存的信息
            //....假设这里已经获取到了
            DataScopeCacheModel cache=...;
            if (ObjectUtil.isEmpty(cache)) {
                this.setSqlString(point, "1!=1");
                return;
            }
            String scriptId = cache.getScriptId();
            //通过缓存的脚本获取脚本内容
            String content="....."
            if (ObjectUtil.isEmpty(content)) {
                this.setSqlString(point, "1!=1");
                return;
            }
            handleDataScope(point, cache, content);
        } catch (Exception e) {
            log.error("数据权限判断错误:{},异常信息:", SecurityUtils.getUserName(), e);
            this.setSqlString(point, "1!=1");
        }

    }
 /**
     * 处理数据权限信息
     *
     * @param joinPoint 切点
     * @param cache     缓存
     * @param content   脚本内容
     */
    protected void handleDataScope(final JoinPoint joinPoint, DataScopeCacheModel  cache, String content) {
        // 获取当前的用户
        //如果是vip,跳过鉴权,这里其实就是当前用户的权限id与目标的权限id取交集
        if (checkSpel(cache.getSpelRole())) {
            return;
        }
        //执行脚本并返回拼接的sql
        String sqlString = this.executeScript(content, cache);
        this.setSqlString(joinPoint, sqlString);
    }

     /**
     * 执行脚本
     *
     * @param script 脚本内容
     * @return 返回拼接的sql
     */
    private String executeScript(String script, DataScopeCacheModel  cache) {
        Binding binding = new Binding();
        binding.setVariable(USER, SecurityUtils.getUserId());
        binding.setVariable(DEPT, SecurityUtils.getDeptId());
        //...略,这里把用户信息与接口的信息传入Groovy脚本
        GroovyShell shell = new GroovyShell(binding);
        return (String) shell.evaluate(script);
    }

4.Groovy脚本的实现

这里仅对一个字段进行了实现,如何存在多个字段进行sql的拼接的情况,这里的sqlString两个字段会相同,这里可以加入具体的权限进行实现,譬如说当前用户,当前用户的部分用户,当前用户的下级等条件。

这里的脚本仅针对了一个字段,如果有多个字段的话,我能想到的就是继续添加脚本,因为字段的数量是可控的但是与字段拼接的值还需要和业务相匹配,如果随着业务的增加,这里的脚本可能会越来越多


/**
 * 自己只能看自己的,部门领导可以看部门下的所有数据
 * @author 玄夜喀拉
 */
def userId = (String) binding.getVariable(DataScopePlusAspect.USER)
def deptId = (String) binding.getVariable(DataScopePlusAspect.DEPT)
def tableAlias = (String) binding.getVariable(DataScopePlusAspect.TABLE_ALIAS)
def fieldAlias = (String) binding.getVariable(DataScopePlusAspect.FIELD_ALIAS)
def andAdd = (Boolean) binding.getVariable(DataScopePlusAspect.AND_ADD)

//部门表
def deptList = ...这里获取部门的列表
//用户表
def userList = ...这里获取用户的列表

//构建部门map和用户map
def deptMap = deptList.stream().collect(Collectors.toMap((SysDept k) -> String.valueOf(k.getDeptId()), Function.identity()))

//获取部门与用户list的映射
def deptUsersMap = userList.stream().collect(Collectors.groupingBy({ SysUser user -> String.valueOf(user.getDeptId()) }, Collectors.mapping({ SysUser user -> String.valueOf(user.getUserId()) }, Collectors.toList())))
//最终可查看的用户数据
Set<String> result = new HashSet<>()

//获取用户的子部门信息
//找出该用户所在部门的子部门
List<String> childDeptIds = new ArrayList<>();
for (SysDept deptItem : deptList) {
    String ancestors = deptItem.getAncestors();
    if (ancestors.contains(String.valueOf(deptId))) {
        childDeptIds.add(String.valueOf(deptItem.getDeptId()));
    }
}
//判断,如果是叶子节点,则只查看该用户自己的数据
if (childDeptIds.size() == 0) {

    //判断该部门的领导是不是当前用户
    def sysDept = deptMap.get(deptId)

    //是否为该部门领导,找出该部门的用户
    if (userId == sysDept.leaderId.toString()) { //是领导
        def userIds = deptUsersMap.get(deptId);
        if (userIds) {
            //获取该部门的所有用户
            result.addAll(userIds)
        }
    } else {
        //不是领导添加自己即可
        result.add(userId)
    }
} else {//有子部门,说明该用户不为普通人,直接找他的部门和子部门的所有用户

    //将自己的部门也加进去
    childDeptIds.add(deptId)

    childDeptIds.each({ child ->
        {
            List<String> userIds = deptUsersMap.get(child)
            if (userIds) {
                result.addAll(userIds)
            }
        }
    })
}

//遍历部门表,查看leader是否相等
deptList.each({ deptInfo ->
    if (String.valueOf(deptInfo.leaderId) == userId && String.valueOf(deptInfo.deptId) != deptId) {
        childDeptIds.add(String.valueOf(deptInfo.deptId))
        childDeptIds.each({ item ->
            List<String> userIds = deptUsersMap.get(item)
            if (userIds) {
                result.addAll(userIds)
            }
        })
    }
})

//拼接用户字符串
def sqlString = StrUtil.join(",", result)

String table = ""
if ("" != tableAlias) {
    table = "${tableAlias}."
}
if ("" == fieldAlias) {
    fieldAlias = "create_by"
}
return "${andAdd ? 'and' : ''} ${table} ${fieldAlias} in (${sqlString})".toString()

总结

Groovy是一个基于JVM的脚本语言,它可以与Java互相调用,相较于原有的方式,使用groovy的方式程序效率相较于若依原版会更加低一点,而且依赖Groovy环境容易出现问题,但是这种实现方式更加灵活可控,能完成一些复杂逻辑的代码。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
基于Flink与Groovy实现全实时动态规则智能营销与风控系统,主要利用了Flink的实时流处理能力和Groovy动态语言特性。 首先,Flink作为一个开源流处理框架,可以支持大规模数据的实时处理和分析。通过Flink的流式计算模型和状态管理机制,我们可以实时地对数据流进行分析和处理。在这个系统中,Flink可以承担数据的实时传输和处理任务,从不同的数据源获取数据,并进行实时的数据清洗、过滤、转换、聚合等操作。 其次,Groovy作为一种动态编程语言,具有灵活的语法和扩展性。在该系统中,我们可以使用Groovy作为规则引擎的脚本语言,编写并执行各种规则。Groovy动态性使得我们可以根据实际情况,在系统运行过程中动态地修改、添加、删除规则,从而实现实时的动态规则调整和更新。 基于上述两个技术,我们可以实现全实时动态规则智能营销与风控系统。该系统可以在实时数据流中根据预先定义或动态添加的规则,判断数据是否满足某些条件,进而触发相应的营销或风控策略。例如,在营销方面,可以根据用户的行为数据动态地识别用户的购买意向,并实时推送个性化的营销信息;在风控方面,可以根据实时的交易数据动态地检测风险事件,实施实时的异常交易监控和风险预警。 综上所述,基于Flink与Groovy的全实时动态规则智能营销与风控系统,可以实现实时的数据处理和规则匹配,有效地提升营销效果和风控能力。该系统具有高效性、灵活性和可扩展性,能够适应不断变化的业务需求和规则变动。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值