那曲檬骨新材料有限公司

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

使用mybatis切片實現數據權限控制

京東云 ? 來源:jf_75140285 ? 作者:jf_75140285 ? 2024-07-09 17:26 ? 次閱讀

一、使用方式

數據權限控制需要對查詢出的數據進行篩選,對業務入侵最少的方式就是利用mybatis或者數據庫連接池的切片對已有業務的sql進行修改。切片邏輯完成后,僅需要在業務中加入少量標記代碼,就可以實現對數據權限的控制。這種修改方式,對老業務的邏輯沒有入侵或只有少量入侵,基本不影響老業務的邏輯和可讀性;對新業務,業務開發人員無需過多關注權限問題,可以集中精力處理業務邏輯。

由于部門代碼中使用的數據庫連接池種類較多,不利于切片控制邏輯的快速完成,而sql拼接的部分基本只有mybatis和java字符串直接拼接兩種方式,因此使用mybatis切片的方式來完成數據權限控制邏輯。在mybatis的mapper文件的接口上添加注解,注解中寫明需要控制的權限種類、要控制的表名、列名即可控制接口的數據權限。

wKgZomaLyPGADDxAAAEr4RL-Yl0024.png

??

由于mybatis的mapper文件中的同一接口在多個地方被調用,有的需要控制數據權限,有的不需要,因此增加一種權限控制方式:通過ThreadLocal傳遞權限控制規則來控制當前sql執行時控制數據權限。

wKgaomaLyPKAXPzWAAC3LRLJclU836.png

??

權限控制規則格式如下:

限權規則code1(表名1.字段名1,表名2.字段名2);限權規則code2(表名3.字段名3,表名4.字段名4)

例如:enterprise(channel.enterprise_code);account(table.column);channel(table3.id)

上下文傳遞工具類如下所示,使用回調的方式傳遞ThreadLocal可以防止使用者忘記清除上下文。

?

public class DataAuthContextUtil {
    /**
     * 不方便使用注解的地方,可以直接使用上下文設置數據規則
     */
    private static ThreadLocal useDataAuth = new ThreadLocal();

    /**
     * 有的sql只在部分情況下需要使用數據權限限制

     * 上下文和注解中均可設置數據權限規則,都設置時,上下文中的優先
     *
     * @param supplier
     */
    public static  T executeSqlWithDataAuthRule(String rule, Supplier supplier) {
        try {
            useDataAuth.set(rule);
            return supplier.get();
        } finally {
            useDataAuth.remove();
        }
    }

    /**
     * 獲取數據權限標志
     *
     * @return
     */
    public static String getUseDataAuthRule() {
        return useDataAuth.get();
    }
}

二、切片實現流程

wKgZomaLyPKANAOVAAEBobDhJAA083.png

??

三、其他技術細節

(1)在切面中獲取原始sql

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import reactor.util.function.Tuple2;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Component
@Intercepts({
//        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
@Slf4j
public class DataAuthInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
            BoundSql boundSql = mappedStatement.getBoundSql(invocation.getArgs()[1]);
            String sql = boundSql.getSql();
        } catch (Exception e) {
            log.error("數據權限添加出錯,當前sql未加數據權限限制!", e);
            throw e;
        }
        return invocation.proceed();
    }
}

(2)將權限項加入原始sql中

使用druid附帶的ast解析功能修改sql,代碼如下

/**
     * 權限限制寫入sql
     *
     * @param sql
     * @param tableAuthMap key:table value1:column value2:values權限項
     * @return
     */
    public static StringBuilder addAuthLimitToSql(String sql, Map>> tableAuthMap) {
        List stmtList = SQLUtils.parseStatements(sql, "mysql");
        StringBuilder authSql = new StringBuilder();
        for (SQLStatement stmt : stmtList) {
            stmt.accept(new MySqlASTVisitorAdapter() {
                @Override
                public boolean visit(MySqlSelectQueryBlock x) {
                    SQLTableSource from = x.getFrom();
                    Set tableList = new HashSet();
                    getTableList(from, tableList);
                    for (String tableName : tableList) {
                        if (tableAuthMap.containsKey(tableName)) {
                            x.addCondition(tableName + "in (...略)");
                        }
                    }
                    return true;
                }
            });
            authSql.append(stmt);
        }
        return authSql;
    }
    
    private static void getTableList(SQLTableSource from, Set tableList) {
        if (from instanceof SQLExprTableSource) {
            SQLExprTableSource tableSource = (SQLExprTableSource) from;
            String name = tableSource.getTableName().replace("`", "");
            tableList.add(name);
            String alias = tableSource.getAlias();
            if (StringUtils.isNotBlank(alias)) {
                tableList.add(alias.replace("`", ""));
            }
        } else if (from instanceof SQLJoinTableSource) {
            SQLJoinTableSource joinTableSource = (SQLJoinTableSource) from;
            getTableList(joinTableSource.getLeft(), tableList);
            getTableList(joinTableSource.getRight(), tableList);
        } else if (from instanceof SQLSubqueryTableSource) {
            SQLSubqueryTableSource tableSource = (SQLSubqueryTableSource) from;
            tableList.add(tableSource.getAlias().replace("`", ""));
        } else if (from instanceof SQLLateralViewTableSource) {
            log.warn("SQLLateralView不用處理");
        } else if (from instanceof SQLUnionQueryTableSource) {
            //union 不需要處理
            log.warn("union不用處理");
        } else if (from instanceof SQLUnnestTableSource) {
            log.warn("Unnest不用處理");
        } else if (from instanceof SQLValuesTableSource) {
            log.warn("Values不用處理");
        } else if (from instanceof SQLWithSubqueryClause) {
            log.warn("子查詢不用處理");
        } else if (from instanceof SQLTableSourceImpl) {
            log.warn("Impl不用處理");
        }
    }
}

(3)將修改過后的sql寫回mybatis

        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
        BoundSql boundSql = ms.getBoundSql(invocation.getArgs()[1]);
        // 組裝 MappedStatement
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), new MySqlSource(boundSql), ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        MappedStatement newMappedStatement = builder.build();
        MetaObject metaObject = MetaObject.forObject(newMappedStatement, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
        metaObject.setValue("sqlSource.boundSql.sql", newSql);
        invocation.getArgs()[0] = newMappedStatement;

?

參考文章: https://blog.csdn.net/e_anjing/article/details/79102693

審核編輯 黃宇

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 權限控制
    +關注

    關注

    0

    文章

    5

    瀏覽量

    6421
  • mybatis
    +關注

    關注

    0

    文章

    62

    瀏覽量

    6745
收藏 人收藏

    評論

    相關推薦

    基于SpringBoot mybatis方式的增刪改查實現

    SpringBoot mybatis方式實現增刪改查
    發表于 06-18 16:56

    數據庫整合Mybatis框架

    微服務 SpringBoot 20(九):整合Mybatis
    發表于 07-16 11:03

    基于規則引擎的實例級權限控制研究

    該文通過比較常用的權限控制實現方法,提出了基于規則引擎的實例級權限控制框架,能靈活實現復雜的安
    發表于 09-03 12:12 ?16次下載

    基于RBAC的限制約束在權限控制中的實現

    本文針對當前權限控制框架在權限控制數據保護方面存在的問題,提出一種新的權限
    發表于 01-15 16:08 ?6次下載

    PLM系統中數據權限控制研究

    基于PLM系統中影響數據權限的4種因素,提出了基于權限規則的動態權限控制機制。為了使數據對象的生
    發表于 03-07 15:17 ?0次下載
    PLM系統中<b class='flag-5'>數據</b><b class='flag-5'>權限</b><b class='flag-5'>控制</b>研究

    基于ThinkPHP的權限控制模塊的設計與實現許宏云

    基于ThinkPHP的權限控制模塊的設計與實現_許宏云
    發表于 03-17 08:00 ?0次下載

    MyBatis實現原理

    本文主要詳細介紹了MyBatis實現原理。mybatis底層還是采用原生jdbc來對數據庫進行操作的,只是通過 SqlSessionFactory,SqlSession Execut
    的頭像 發表于 02-24 11:25 ?6529次閱讀
    <b class='flag-5'>MyBatis</b>的<b class='flag-5'>實現</b>原理

    Fluent Mybatis、原生MybatisMybatis Plus對比

    使用fluent mybatis可以不用寫具體的xml文件,通過java api可以構造出比較復雜的業務sql語句,做到代碼邏輯和sql邏輯的合一。不再需要在Dao中組裝查詢或更新操作,在xml或
    的頭像 發表于 09-15 15:41 ?1481次閱讀

    權限系統中的數據權限設計經驗分享

    RBAC模型 數據權限 數據規則 關聯資源、用戶 繼續優化 小結 在項目實際開發中我們不光要控制一個用戶能訪問哪些資源,還需要控制用戶只能訪
    的頭像 發表于 10-11 17:50 ?1840次閱讀

    SpringBoot+Mybatis如何實現流式查詢?

    使用mybatis作為持久層的框架時,通過mybatis執行查詢數據的請求執行成功后,mybatis返回的結果集不是一個集合或對象,而是一個迭代器,可以通過遍歷迭代器來取出結果集
    的頭像 發表于 06-12 09:57 ?1319次閱讀

    基于Mybatis攔截器實現數據范圍權限

    前端的菜單和按鈕權限都可以通過配置來實現,但很多時候,后臺查詢數據數據權限需要通過手動添加SQL來
    的頭像 發表于 06-20 09:57 ?1411次閱讀
    基于<b class='flag-5'>Mybatis</b>攔截器<b class='flag-5'>實現</b><b class='flag-5'>數據</b>范圍<b class='flag-5'>權限</b>

    如何實現基于Mybatis攔截器實現數據范圍權限呢?

    前端的菜單和按鈕權限都可以通過配置來實現,但很多時候,后臺查詢數據數據權限需要通過手動添加SQL來
    的頭像 發表于 06-20 09:59 ?1300次閱讀
    如何<b class='flag-5'>實現</b>基于<b class='flag-5'>Mybatis</b>攔截器<b class='flag-5'>實現</b><b class='flag-5'>數據</b>范圍<b class='flag-5'>權限</b>呢?

    如何利用MyBatis Plus去實現數據權限控制呢?

    平時開發中遇到根據當前用戶的角色,只能查看數據權限范圍的數據需求。列表實現方案有兩種,一是在開發初期就做好判斷賽選,但如果這個需求是中途加的,或不希望每個接口都加一遍,就可以方案二加攔
    的頭像 發表于 08-23 10:40 ?1096次閱讀
    如何利用<b class='flag-5'>MyBatis</b> Plus去<b class='flag-5'>實現</b><b class='flag-5'>數據</b><b class='flag-5'>權限</b><b class='flag-5'>控制</b>呢?

    mybatis的dao能重載嗎

    不同需求的數據操作。 重載是指在同一個類中定義了多個方法,它們具有相同的名稱但具有不同的參數。重載允許使用相同的方法名來處理不同類型和數量的參數,以提供更加靈活的操作。 在MyBatis的DAO中,我們可以通過重載方法來實現不同
    的頭像 發表于 12-03 11:51 ?1354次閱讀

    鴻蒙開發組件:DataAbility權限控制

    DataAbility提供數據服務,并不是所有的Ability都有權限讀寫它,DataAbility有一套權限控制機制來保證數據安全。分為靜
    的頭像 發表于 06-21 10:30 ?385次閱讀
    鴻蒙開發組件:DataAbility<b class='flag-5'>權限</b><b class='flag-5'>控制</b>
    华盛顿百家乐官网的玩法技巧和规则 | 皇冠投注平台| 百家乐总厂在哪里| 获嘉县| A8百家乐娱乐平台| 百家乐官网神算子| 线上百家乐怎么玩| 玩百家乐官网去哪个娱乐城最安全| 久久棋牌评测| 夜总会百家乐官网的玩法技巧和规则 | 带有百家乐官网的棋牌游戏有哪些| 大发888娱乐城论坛| 百家乐官网号公| 太湖县| 威尼斯人娱乐城活动lm0| 皇马百家乐官网的玩法技巧和规则 | 百家乐官网服务区| 银河国际娱乐场| 百家乐高额投注| 百家乐官网真人娱乐城陈小春| 联众棋牌游戏大厅| 玩百家乐五湖四海娱乐城| 明升百家乐官网QQ群| 棋牌中心| 线上百家乐代理| 百家乐官网强弱走势图| 仕达屋娱乐城| 百家乐桌套装| 百家乐网上公式| 做百家乐官网网上投注| 大发888真钱赌场娱乐网规则| 天地人百家乐现金网| 百家乐官网庄9点| 大发888娱乐城大奖| 百家乐视频麻将游戏| 买百家乐官网程序| 眉山市| 大发888官方sscptdf88yb| 百家乐偷码| 百家乐官网社区| 最好的百家乐官网博彩公司|