数据权限——Mybatis拦截器实现

一、需求背景介绍

1、需求介绍
需要实现数据权限管理,包含角色:普通用户、组长、管理员。其中普通用户只能看到自己创建的项目,组长能看到自己所管理的普通用户创建的项目,管理员能看到所有项目。相关表为:项目表(包含责任人owner字段,owner所属组group字段)、用户表(包含组id)、组长信息表、管理员表。
2、方案设计
采用Mybatis拦截器,在请求查询sql后拼条件。
(1)如果当前用户为普通用户,查询项目时拼上条件owner=user;
(2)如果当前用户为组长,查找当前user所管理的组list,拼上条件group in (…);
(3)如果当前用户为管理员,不拼额外条件。

二、Mybatis拦截器介绍

关于Mybatis拦截器,网上有不少博客介绍,本文不做详细解释,这里贴一个相关链接Mabatis拦截器
Mybatis拦截器并不是每个对象里面的方法都可以被拦截的。Mybatis拦截器只能拦截Executor、ParameterHandler、StatementHandler、ResultSetHandler四个对象里的方法。

三、代码

本文采用拦截StatementHandler里的prepare方法。(不会影响分页结果。代码做了删减,无法直接使用,可做参考)

import java.io.StringReader;
import java.sql.Connection;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Properties;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;

import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler;
import com.google.common.collect.Lists;

import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.update.Update;

@Slf4j
@Intercepts({@Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = {Connection.class, Integer.class})})
@Component
public class AuthInterceptor extends AbstractSqlParserHandler implements Interceptor {

    public AuthInterceptor() {
    }

    /**
     * 自定义的逻辑处理
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
        //映射工具
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        //中间去判断多层
        this.sqlParser(metaObject);
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        //获取到执行的sql
        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        CCJSqlParserManager parserManager = new CCJSqlParserManager();

        //select
        if (SqlCommandType.SELECT == mappedStatement.getSqlCommandType() && StatementType.CALLABLE != mappedStatement
                .getStatementType()) {
            Select select = (Select) parserManager.parse(new StringReader(boundSql.getSql()));
            PlainSelect selectBody = (PlainSelect) select.getSelectBody();
            // 获取表名
            String tableName = ((Table) selectBody.getFromItem()).getName();
            // 这里对需要拦截的表做下筛选
            if (StringUtils.equalsAny(tableName, "project", "supplier")) {
                log.info("权限拦截...");
                String aliasName = ""; // 表别名
                try {
                    aliasName = selectBody.getFromItem().getAlias().getName() + ".";
                } catch (Exception ignored) {
                }
                // 需要拼的条件
                String whereSql = getNewSql(tableName, aliasName);
                // 把要拼的条件设置进去
                if (StringUtils.isNotEmpty(whereSql)) {
                    Expression whereExpression = CCJSqlParserUtil.parseCondExpression(whereSql);
                    if (null != selectBody.getWhere()) {
                        selectBody.setWhere(new AndExpression(selectBody.getWhere(), new Parenthesis(whereExpression)));
                    } else {
                        selectBody.setWhere(whereExpression);
                    }
                }
                metaObject.setValue("delegate.boundSql.sql", selectBody.toString());
            }
        }
        return invocation.proceed();
    }

    private String getNewSql(String tableName, String aliasName) {
        StringBuilder whereSql = new StringBuilder();
        UserInfo userInfo = UserUtil.getCurrentUser();
        String userRole = userInfo.getRole(); // 当前用户角色,逗号分隔
        String groupList = userInfo.getGroup(); // 当前用户所管理的组,逗号分隔
        switch (tableName) {
            case "project":
                if (userRole.contains(Role.manager.getCode())) {
                    
                } else if (userRole.contains(Role.leader.getCode())) {
                    List<String> purchaseTeamList = Arrays.asList(sourcingProjectScope.split(","));
                    whereSql.append("(").append(aliasName).append("group in ('").append(String.join("','",
                            purchaseTeamList)).append("')");
                } else {
                    whereSql.append(aliasName).append("owner = '").append(userInfo.getUsername()).append("'");
                }
                break;
            case "supplier":
                /*
                如果拦截的是别的表,需要做什么操作,可以自己加。
                 */
                break;
            default:
                return null;
        }
        return whereSql.toString();
    }

    @Override
    public Object plugin(Object target) {
        return target instanceof StatementHandler ? Plugin.wrap(target, this) : target;
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

四、关于操作权限

数据权限做了简化处理,实际比上文介绍的复杂。比如组长及管理员不能查看普通用户新建、取消等状态的项目,组长及管理员只有只读权限没有编辑权限等。这个就涉及到操作权限了。
方案:将页面上的按钮、服务、Tab等资源赋予一个唯一资源id。对于一个用户所拥有的角色,该角色具有的操作权限资源集合作为A。对于只读角色,所能操作的资源集合作为B。那么一个用户进入到项目,owner字段如果不是当前user,则该用户不能编辑,只有只读权限,该用户所拥有的资源集合就为A∩B。将该资源集合返回给前端,前端给予控制展示。
另外,如果系统只允许用户表中存在的用户使用,那么可以在拦截器中做一层过滤,不是用户表中的用户,返回403跳转无权限页。在网关也可以做一层操作权限控制,如果当前用户所请求的uri不在该用户所能操作的资源uri集合A中,则直接拒绝。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值