mybatis整合数据权限

现在很多企业级应用都需要拦截数据权限, 只有配置了相应数据权限的人才能看到该数据

关于数据权限的实现, 个人想了两种实现方式

第一种是基于AOP, 配置相应的注解, 在切面中将数据权限的参数值强制设置到请求参数中去, 然后dao层利用mybatis的动态sql, 将权限值拼接进去, 该方案有前提条件, 数据权限控制的字段必须放到基类中, 其他的对象要继承该基类, Mapper.xml必须抽取一个公用的, 其他的Mapper需要引用该mapper文件作为权限控制(否则每个类和Mapper.xml都要独自维护一套, 不便于后续扩展和维护)

第二种是基于mybatis的拦截器, 拦截sql后将数据权限控制的sql拼接进去, 基于mybatis拦截器拼接sql的难点在于数据权限sql和原sql的拼接, 网上很多版本都是select * from (原sql) where 数据权限sql, 这种实际上并不可取, 一旦出现分页, 或者最终查出的结果集不包含权限控制字段的话, 就会出现bug

这里使用的是正则表达式去匹配后拼接权限sql, 其他的可以参考网上mybatis拦截器的方案

/**
 * sql解析器
 *
 * @author wang.js on 2019/5/8.
 * @version 1.0
 */
public class SqlParser {

    private SqlParser() {
    }

    private static final String SQL_WHERE = "WHERE";

    private static final String SQL_LEFT_JOIN = "LEFT JOIN ";

    /**
     * 将数据权限的sql拼进原sql中
     *
     * @param originSql    原sql
     * @param privilegeSql 数据权限sql
     * @return String
     */
    public static String handlerSql(String originSql, String privilegeSql) {
        if (originSql.endsWith(";")) {
            originSql = originSql.substring(0, originSql.lastIndexOf(";"));
        }
        originSql = originSql.replace("\t", " ").replace("\n", " ");
        originSql = originSql.replaceAll(" {2,}", " ");
        originSql = originSql.replaceAll(" where ", " " + SQL_WHERE + " ");
        originSql = addWhere(originSql);
        originSql = originSql.replace("left", "LEFT");
        originSql = originSql.replace("join", "JOIN");
        originSql = originSql.replaceAll("LEFT[ ]+JOIN[ ]+", SQL_LEFT_JOIN);

        List<String> matcherList = matcherTableSql(originSql);
        for (String matcherSql : matcherList) {
            if (originSql.contains(SQL_LEFT_JOIN + matcherSql)) {
                continue;
            }
            String newMatcherSql = mergeSql(matcherSql, privilegeSql);
            originSql = originSql.replace(matcherSql, newMatcherSql);
        }
        return originSql;
    }

    /**
     * 添加where关键字
     *
     * @param originSql 原始sql
     * @return String
     */
    private static String addWhere(String originSql) {
        if (originSql.contains("WHERE")) {
            return originSql;
        }
        String[] split = originSql.split(" ");
        for (int i = 0; i < split.length; i++) {
            if (split[i].equalsIgnoreCase("GROUP") && (i + 1) < split.length && split[i + 1].equalsIgnoreCase("BY")) {
                return originSql.replaceAll(split[i], "WHERE 1=1 GROUP");
            }
            if (split[i].equalsIgnoreCase("ORDER") && (i + 1) < split.length && split[i + 1].equalsIgnoreCase("BY")) {
                return originSql.replaceAll(split[i], "WHERE 1=1 ORDER");
            }
            if (split[i].equalsIgnoreCase("LIMIT")) {
                return originSql.replaceAll(split[i], "WHERE 1=1 LIMIT");
            }
        }
        return originSql + " WHERE 1=1";
    }

    /**
     * 合并sql
     *
     * @param originSql    原sql
     * @param privilegeSql 数据权限sql
     * @return String
     */
    private static String mergeSql(String originSql, String privilegeSql) {
        return originSql.replace(SQL_WHERE, SQL_WHERE + " " + privilegeSql);
    }

    /**
     * 配置符合
     *
     * @param originSql 原sql
     * @return List<String>
     */
    private static List<String> matcherTableSql(String originSql) {
        List<String> matcharList = new ArrayList<>();
        String regex = "\\w[ ,](.*?)WHERE";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(originSql);
        while (matcher.find()) {
            matcharList.add(matcher.group());
        }
        return matcharList;
    }

}

写个测试类

@Test
public void t1() {
    String privilegeSql = " brand_code in (1, 2, 3) and region_code in (2, 3) and";
    String originSql = "select * from (select t1, t2, t3 from tableA where 1=1) t1, (select t1, t2, t3 from tableB s where 1=1) t2";
    //      String originSql = "select t1, t2, t3 from tableA";
    System.out.println(SqlParser.handlerSql(originSql, privilegeSql));
}

测试后输出的结果为 select * from (select t1, t2, t3 from tableA WHERE brandcode in (1, 2, 3) and regioncode in (2, 3) and 1=1) t1, (select t1, t2, t3 from tableB s WHERE brandcode in (1, 2, 3) and regioncode in (2, 3) and 1=1) t2

这里需要强调的是, 必须保证每条需要权限控制的语句都含有where关键字, 否则数据权限sql拼接不进去

总结 第一种基于AOP的方案需要维护基类和Mapper的通用代码, 其他的必须继承或引用这两个文件, 但是好处是不容易出现bug

第二种基于mybatis拦截器的方案需要保证每条需要权限控制的sql都有where关键字, 好处是mapper.xml和基类都没有强制性要求

具体使用哪一种就看实际项目需要了

posted on 2019-05-18 12:53  忘记送  阅读(1517)  评论(0编辑  收藏  举报