千家信息网

Druid多数据源下Sql防火墙导致异常的示例分析

发表于:2024-11-30 作者:千家信息网编辑
千家信息网最后更新 2024年11月30日,本篇文章给大家分享的是有关Druid多数据源下Sql防火墙导致异常的示例分析,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。问题原因由于应
千家信息网最后更新 2024年11月30日Druid多数据源下Sql防火墙导致异常的示例分析

本篇文章给大家分享的是有关Druid多数据源下Sql防火墙导致异常的示例分析,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。

问题原因

  1. 由于应用组件同时使用了两个数据源,Oracle和Mysql,而之前一直都是Oracle,近期引入了Mysql;

  2. 应用组件生产环境配置开启了Druid的Sql防火墙配置,配置项为:spring.datasource.druid.filter.wall.enabled=true ,由于测试环境未开启,因此将问题带到了生产上面。

开启Sql防火墙后,在数据源初始化的时候会初始化一个 WallFilter .

    @Bean    @ConfigurationProperties("spring.datasource.druid.filter.wall")    @ConditionalOnProperty(        prefix = "spring.datasource.druid.filter.wall",        name = {"enabled"}    )    @ConditionalOnMissingBean    public WallFilter wallFilter(WallConfig wallConfig) {        WallFilter filter = new WallFilter();        filter.setConfig(wallConfig);        return filter;    }

问题表象

在近期版本上线后,一些跑了好几年的sql突然一夜之间报错了,守版本的弟兄遇到这种问题后,难免大惊失色,无奈之下,当晚只好回滚了版本。

下面我们来看下,已经跑了几年的sql当时报了什么错:

at com.alibaba.druid.wall.WallFilter.checkInternal(WallFilter.java:798)at com.alibaba.druid.wall.WallFilter.connection_prepareStatement(WallFilter.java:251)at com.alibaba.druid.filter.FilterChainImpl.connection_prepareStatement(FilterChainImpl.java:473)at com.alibaba.druid.filter.FilterAdapter.connection_prepareStatement(FilterAdapter.java:929)at com.alibaba.druid.filter.FilterEventAdapter.connection_prepareStatement(FilterEventAdapter.java:122)at com.alibaba.druid.filter.FilterChainImpl.connection_prepareStatement(FilterChainImpl.java:473)at com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl.prepareStatement(ConnectionProxyImpl.java:342)at com.alibaba.druid.pool.DruidPooledConnection.prepareStatement(DruidPooledConnection.java:350)at org.apache.ibatis.executor.statement.PreparedStatementHandler.instantiateStatement(PreparedStatementHandler.java:87)at org.apache.ibatis.executor.statement.BaseStatementHandler.prepare(BaseStatementHandler.java:88)at org.apache.ibatis.executor.statement.RoutingStatementHandler.prepare(RoutingStatementHandler.java:59)at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:85)at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:49)at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117)at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76)at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:198)at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:185)at sun.reflect.GeneratedMethodAccessor408.invoke(Unknown Source)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433)... 121 common frames omittedCaused by: com.alibaba.druid.sql.parser.ParserException: syntax error, error in :' USING(select ? as o', expect USING, actual IDENTIFIER pos 61, line 2, column 39, token IDENTIFIER USINGat com.alibaba.druid.sql.parser.SQLParser.printError(SQLParser.java:284)at com.alibaba.druid.sql.parser.SQLParser.accept(SQLParser.java:292)at com.alibaba.druid.sql.parser.SQLStatementParser.parseMerge(SQLStatementParser.java:2879)at com.alibaba.druid.sql.parser.SQLStatementParser.parseStatementList(SQLStatementParser.java:225)at com.alibaba.druid.sql.parser.SQLStatementParser.parseStatementList(SQLStatementParser.java:81)at com.alibaba.druid.wall.WallProvider.checkInternal(WallProvider.java:622)at com.alibaba.druid.wall.WallProvider.check(WallProvider.java:576)at com.alibaba.druid.wall.WallFilter.checkInternal(WallFilter.java:785)... 141 common frames omitted

看到上面的异常堆栈,抛出的异常的意思是:对该SQL进行解析的时候期望接受的USING关键字,但实际上在关键字库里面确没有找到USING关键字,而是把USING当成了标识符IDENTIFIER.

问题分析

打开SQLStatementParser 及 SQLParser类源码发现有以下信息:

com.alibaba.druid.sql.parser.SQLStatementParser#parseMerge

关键问题在这里,为什么关键字的词里没有找到USING关键字? 通过debug发现在第一次访问数据的时候会调用com.alibaba.druid.pool.DruidDataSource#init 方法, 该方法里有这一句:

public void init() throws SQLException {        if (inited) {            return;        }        final ReentrantLock lock = this.lock;        try {            lock.lockInterruptibly();        } catch (InterruptedException e) {            throw new SQLException("interrupt", e);        }        boolean init = false;        try {            if (inited) {                return;            }            initStackTrace = Utils.toString(Thread.currentThread().getStackTrace());            this.id = DruidDriver.createDataSourceId();            if (this.id > 1) {                long delta = (this.id - 1) * 100000;                this.connectionIdSeed.addAndGet(delta);                this.statementIdSeed.addAndGet(delta);                this.resultSetIdSeed.addAndGet(delta);                this.transactionIdSeed.addAndGet(delta);            }            if (this.jdbcUrl != null) {                this.jdbcUrl = this.jdbcUrl.trim();                initFromWrapDriverUrl();            }            // 重点在这里            for (Filter filter : filters) {                filter.init(this);            }

这句话的意思是初始化配置的Filter,其中我们启用的WallFilter就是在这里初始化的。

接下来我们进入WallFilter 的 init方法:

 @Override    public synchronized void init(DataSourceProxy dataSource) {        if (null == dataSource) {            LOG.error("dataSource should not be null");            return;        }        if (this.dbType == null || this.dbType.trim().length() == 0) {                                          // dbType 取决于第一次调用此方法的数据源的类型            if (dataSource.getDbType() != null) {                this.dbType = dataSource.getDbType();            } else {                this.dbType = JdbcUtils.getDbType(dataSource.getRawJdbcUrl(), "");            }        }        if (dbType == null) {            dbType = JdbcUtils.getDbType(dataSource.getUrl(), null);        }        if (JdbcUtils.MYSQL.equals(dbType) || //            JdbcUtils.MARIADB.equals(dbType) || //            JdbcUtils.H2.equals(dbType)) {            if (config == null) {                config = new WallConfig(MySqlWallProvider.DEFAULT_CONFIG_DIR);            }                                                // 初始化Mysql类型的防火墙            provider = new MySqlWallProvider(config);        } else if (JdbcUtils.ORACLE.equals(dbType) || JdbcUtils.ALI_ORACLE.equals(dbType)) {            if (config == null) {                config = new WallConfig(OracleWallProvider.DEFAULT_CONFIG_DIR);            }

我们发现此方法加了 synchronized,并且里面有一个关键的属性 dbType。

由于WallFilter只有一个实例,此处的dbType是成员变量,代码中判断为null时才从当前dataSource中获取;

那么问题来了,我们现在有两个 DruidDataSource 实例,那么在初始化时就会调用WallFilter的初始化,但是尽管调用多次 WallFilterinit 方法,但是从上面代码得知dbType仅取决于第一次 DruidDataSource 调用WallFilter 的 init 方法时 dataSource.getDbType() 的值。

分析到这里,你看出什么问题了?

是的,如果当我们第一个访问的数据源是Mysql时,那么不管后续的DruidDataSource多次调用WallFilter 的 init 方法,dbType永远不会改变。

所以导致,我们用的永远都是 MySqlWallProvider ,也就是说我们用的SQL防火墙是Mysql类型的,所以后续对SQL的语法解析也用到了MySqlLexer 词法解析器,该解析器的关键字库里面是没有USING这个关键字的,所以会报错 USING 语法错误。

解决方案

开始我也说了,本次问题是部分实例报sql 异常,这又是为什么呢?

这是因为,这和你第一次访问的数据有关,如果第一次访问的是Mysql库,那后面在访问Oracle时候就会有这个问题。

如何解决这个问题呢?这里给出两种方案。

  1. 可以把spring.datasource.druid.filter.wall.enabled=true 这个配置设置成false 或去掉该配置,这也是最简单快捷的方式;

  2. 对WallFilter和StatFilter 两个类进行拓展和相关逻辑优化,需要编程,未实施。

以上就是Druid多数据源下Sql防火墙导致异常的示例分析,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注行业资讯频道。

问题 关键 数据 数据源 防火墙 防火 方法 配置 关键字 第一次 分析 时候 两个 实例 版本 类型 示例 代码 取决于 字库 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 广州软件开发公 金山区特殊软件开发怎么样 可用的人脸是被数据库 个人网站数据库安全 软件开发一定要有资质吗 NTP服务器异常怎么解决 服务器安全狗+默认密码 计算机网络技术课程实训报告 鹤壁网络技术调试 上海 美恒大厦 网络安全 加密狗更换服务器 万方数据库如何检索外文文献 宣城通信软件开发外包公司 数据库对象不理人表情包 5g网络技术谁开发的 在网络安全中常用的关键技术 excel选中大批量数据库 北京昊远兴达网络技术 央企的软件开发公司意义大吗 数据库层透明加密 可用的人脸是被数据库 软件开发一定要学高数吗 军运会学生网络安全教育 服务器服务查询乱码 数据库备份文件几天大小不变化 淄博瓷砖软件开发服务 闪电网络无法连接服务器 哈尔滨网络安全技术提升收费标准 如何查看服务器是否开启最大性能 优炫数据库安全使用
0