闲碎记事本 闲碎记事本
首页
  • JAVA
  • Cloudflare
  • 学完再改一遍UI
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

YAN

我要偷偷记录...
首页
  • JAVA
  • Cloudflare
  • 学完再改一遍UI
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • java

    • SpringBoot

    • SpringSecurity

    • MybatisPlus

      • 租户拦截器
      • 动态表名
      • SQL阻断器
    • Netty

    • sip

    • 其他

  • linux

  • docker

  • redis

  • nginx

  • mysql

  • 其他

  • 环境搭建

  • 知识库
  • java
  • MybatisPlus
Yan
2023-03-06

SQL阻断器


/*
 * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.baomidou.mybatisplus.extension.plugins.inner;

import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.IsNullExpression;
import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.update.Update;
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 java.sql.Connection;

/**
 * 攻击 SQL 阻断解析器,防止全表更新与删除
 *  extends JsqlParserSupport  得到 SQL 解析功能
 *    对 processDelete ,processUpdate 进行重新,进行条件判断操作
 * @author hubin
 * @since 3.4.0
 */
public class BlockAttackInnerInterceptor extends JsqlParserSupport implements InnerInterceptor {

    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        // 将 ibatis StatementHandler 封装成 MPStatementHandler
        PluginUtils.MPStatementHandler handler = PluginUtils.mpStatementHandler(sh);
        //获取映射语句
        MappedStatement ms = handler.mappedStatement();
        //得到sql命令类型
        SqlCommandType sct = ms.getSqlCommandType();
        //只操作 update 和 delete 语句
        if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
            //判断是否是忽略拦截器的SQL
            if (InterceptorIgnoreHelper.willIgnoreBlockAttack(ms.getId())) return;
            //得到原生SQL
            BoundSql boundSql = handler.boundSql();
            //解析多条SQL
            //此处不接受返回值,也就是说不依赖处理结果,仅仅只是做判断功能
            //实质是:
            // 1:super.parserMulti 遍历执行 parserSingle ,
            // 2:super.parserSingle 通过 CCJSqlParserUtil.parse(sql); 将SQL解析成 net.sf.jsqlparser.parser.Statement (包含了SQL的属性)
            // 3:super.parserSingle 调用 super.processParser 并根据SQL类型触发processDelete,processUpdate
            //    (为什么不触发查询和新增,因为上面方法上做了判断啊,宝)
            parserMulti(boundSql.getSql(), null);
        }
    }

    @Override
    protected void processDelete(Delete delete, int index, String sql, Object obj) {
        //删除过程处理
        this.checkWhere(delete.getTable().getName(), delete.getWhere(), "Prohibition of full table deletion");
    }

    @Override
    protected void processUpdate(Update update, int index, String sql, Object obj) {
        //修改过程处理
        this.checkWhere(update.getTable().getName(), update.getWhere(), "Prohibition of table update operation");
    }

    protected void checkWhere(String tableName, Expression where, String ex) {
        // Assert.isFalse:断言 fullMatch 结果一定时false ,否则抛出异常
        // getTableLogicField : 得到当前表的逻辑删除字段
        Assert.isFalse(this.fullMatch(where, this.getTableLogicField(tableName)), ex);
    }

    private boolean fullMatch(Expression where, String logicField) {

        if (where == null) {
            //没有条件 不处理,返回 true ,与断言false 违背 ,抛出异常
            return true;
        }

        if (StringUtils.isNotBlank(logicField)) {
            //有逻辑删除字段 假如: logicField == is_delete
            //
            if (where instanceof BinaryExpression) {
                BinaryExpression binaryExpression = (BinaryExpression) where;
                // example : is_delete  not xxx  或者   is_delete like   xxx  等。。
                // 禁止使用 逻辑删除字段 做此类运算
                if (StringUtils.equals(binaryExpression.getLeftExpression().toString(), logicField) ||
                    StringUtils.equals(binaryExpression.getRightExpression().toString(), logicField)
                ) {
                    return true;
                }
            }

            if (where instanceof IsNullExpression) {
                IsNullExpression binaryExpression = (IsNullExpression) where;
                // example : is_delete is not null  或者   is_delete isnull
                // 禁止使用 逻辑删除字段 做 is not null 或者 isnull 运算
                // 如果成立则 与断言false 违背 ,抛出异常
                if (StringUtils.equals(binaryExpression.getLeftExpression().toString(), logicField)) {
                    return true;
                }
            }
        }

        //无逻辑删除字段
        if (where instanceof EqualsTo) {
            // example: 1=1
            EqualsTo equalsTo = (EqualsTo) where;
            //如示例 : 在 = 的情况下,左右两边条件恒成立, 与断言false 违背 ,抛出异常
            return StringUtils.equals(equalsTo.getLeftExpression().toString(), equalsTo.getRightExpression().toString());
        } else if (where instanceof NotEqualsTo) {
            // example: 1 != 2
            NotEqualsTo notEqualsTo = (NotEqualsTo) where;
            //如示例 : 在 1= 的情况下,左右两边条件恒不成立, ,与断言false 违背 ,抛出异常
            return !StringUtils.equals(notEqualsTo.getLeftExpression().toString(), notEqualsTo.getRightExpression().toString());
        } else if (where instanceof OrExpression) {
            OrExpression orExpression = (OrExpression) where;
            // 在 or 的情况下  不可直接判断, 1=2 or 2=2
            // 将 or 左右两边条件拆分后,再次进行 fullMatch 运算
            // 如有 一边成立 即条件成立
            return fullMatch(orExpression.getLeftExpression(), logicField) || fullMatch(orExpression.getRightExpression(), logicField);
        } else if (where instanceof AndExpression) {
            // 在 and  的情况下 不可直接判断  1=1 and 2=2
            // 将 or 左右两边条件拆分后,再次进行 fullMatch 运算
            // 如有 两边都成立 即条件成立
            AndExpression andExpression = (AndExpression) where;
            return fullMatch(andExpression.getLeftExpression(), logicField) && fullMatch(andExpression.getRightExpression(), logicField);
        } else if (where instanceof Parenthesis) {
            // example: (1 = 1)
            // 如示例 遇到()条件,得到内部表达式  1 = 1(EqualsTo) 进行 fullMatch
            Parenthesis parenthesis = (Parenthesis) where;
            return fullMatch(parenthesis.getExpression(), logicField);
        }
        //以上条件都不满足,返回false,与断言一致,不抛出异常
        return false;
    }



    /**
     * 获取表名中的逻辑删除字段
     *
     * @param tableName 表名
     * @return 逻辑删除字段
     */
    private String getTableLogicField(String tableName) {
        //如方法注释 不详解
        if (StringUtils.isBlank(tableName)) {
            return StringPool.EMPTY;
        }
        TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName);
        if (tableInfo == null || !tableInfo.isWithLogicDelete() || tableInfo.getLogicDeleteFieldInfo() == null) {
            return StringPool.EMPTY;
        }
        return tableInfo.getLogicDeleteFieldInfo().getColumn();
    }
}


上次更新: 2025/05/14, 01:34:05
动态表名
netty记录

← 动态表名 netty记录→

最近更新
01
Caddy操作指南
04-25
02
Swap空间
04-22
03
Alist使用
04-21
更多文章>
Theme by Vdoing | Copyright © 2022-2025 YAN | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式