美文网首页
springboot进行数据库连接配置的自定义加密

springboot进行数据库连接配置的自定义加密

作者: 东本三月 | 来源:发表于2021-09-29 15:47 被阅读0次

    经验备忘,仅供参考

    1.场景

    配置文件的数据库连接的用户名和密码需要改成密文,提高安全性.
    加解密使用一个工具类来实现,并非是泛用的加解密过程,不方便用插件.

    2.思路

    1.找到从配置文件获取用户名和密码的逻辑,或者使用用户名和密码创建数据源的逻辑
    2.重写对应逻辑的源码类,增加解密的步骤

    3.项目环境

    Spring Boot 2.4.2

            <!-- mybatis-plus -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
            </dependency>
    
            <!-- 动态数据源 -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            </dependency>
    
            <!-- 阿里数据库连接池 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${druid.version}</version>
            </dependency>
    
            <!-- postgresql驱动包 -->
            <dependency>
                <groupId>org.postgresql</groupId>
                <artifactId>postgresql</artifactId>
            </dependency>
    

    项目实际使用了多数据源功能

    #默认数据源
    spring.datasource.dynamic.datasource.postgresql.url=jdbc:postgresql://0.0.0.0:0/demo?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
    spring.datasource.dynamic.datasource.postgresql.username=81c5f00b41ee312
    spring.datasource.dynamic.datasource.postgresql.password=3bbb7b271bf65ddda9cba27dad0bf20e23e84f85d3f7fb4
    
    spring.datasource.dynamic.datasource.prod_01.url=jdbc:postgresql://0.0.0.0:0/demo?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
    spring.datasource.dynamic.datasource.prod_01.username=81c5f00b41ee312
    spring.datasource.dynamic.datasource.prod_01.password=3bbb7b271bf65ddda9cba27dad0bf20e23e84f85d3f7fb4
    
    spring.datasource.dynamic.datasource.prod_02.url=jdbc:postgresql://10.0.0.0:0/demo?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
    spring.datasource.dynamic.datasource.prod_02.username=6e4398db9ca4d1d
    spring.datasource.dynamic.datasource.prod_02.password=c79d198ea20e940c8d0fcb495cef9ab
    

    4.确认创建数据源逻辑

    将数据库连接改成错误的
    查看异常报错信息,进入报错的类
    报错节点增加断点,debug进入断点,由断点向调用链的上方回溯
    在疑似地方继续增加断点,反复debug
    最终找到的类是(也可以去改其他类的逻辑):
    com.baomidou.dynamic.datasource.creator.DruidDataSourceCreator

    *
     * Copyright © 2018 organization baomidou
     *
     * 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.dynamic.datasource.creator;
    
    import com.alibaba.druid.filter.Filter;
    import com.alibaba.druid.filter.logging.Slf4jLogFilter;
    import com.alibaba.druid.filter.stat.StatFilter;
    import com.alibaba.druid.pool.DruidDataSource;
    import com.alibaba.druid.wall.WallConfig;
    import com.alibaba.druid.wall.WallFilter;
    import com.baomidou.dynamic.datasource.exception.ErrorCreateDataSourceException;
    import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
    import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.druid.DruidConfig;
    import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.druid.DruidSlf4jConfig;
    import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.druid.DruidWallConfigUtil;
    import lombok.Data;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    import org.springframework.util.StringUtils;
    
    import javax.sql.DataSource;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Properties;
    
    import static com.baomidou.dynamic.datasource.support.DdConstants.DRUID_DATASOURCE;
    
    /**
     * Druid数据源创建器
     *
     * @author TaoYu
     * @since 2020/1/21
     */
    @Data
    public class DruidDataSourceCreator implements DataSourceCreator {
    
        private static Boolean druidExists = false;
    
        static {
            try {
                Class.forName(DRUID_DATASOURCE);
                druidExists = true;
            } catch (ClassNotFoundException ignored) {
            }
        }
    
        private DruidConfig gConfig;
    
        @Autowired(required = false)
        private ApplicationContext applicationContext;
    
        public DruidDataSourceCreator(DruidConfig gConfig) {
            this.gConfig = gConfig;
        }
    
        @Override
        public DataSource createDataSource(DataSourceProperty dataSourceProperty) {
            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setUsername(dataSourceProperty.getUsername());
            dataSource.setPassword(dataSourceProperty.getPassword());
            dataSource.setUrl(dataSourceProperty.getUrl());
            dataSource.setName(dataSourceProperty.getPoolName());
            String driverClassName = dataSourceProperty.getDriverClassName();
            if (!StringUtils.isEmpty(driverClassName)) {
                dataSource.setDriverClassName(driverClassName);
            }
            DruidConfig config = dataSourceProperty.getDruid();
            Properties properties = config.toProperties(gConfig);
    
            List<Filter> proxyFilters = this.initFilters(dataSourceProperty, properties);
            dataSource.setProxyFilters(proxyFilters);
    
            dataSource.configFromPropety(properties);
            //连接参数单独设置
            dataSource.setConnectProperties(config.getConnectionProperties());
            //设置druid内置properties不支持的的参数
            this.setParam(dataSource, config);
    
            if (!dataSourceProperty.getLazy()) {
                try {
                    dataSource.init();
                } catch (SQLException e) {
                    throw new ErrorCreateDataSourceException("druid create error", e);
                }
            }
            return dataSource;
        }
    
        private List<Filter> initFilters(DataSourceProperty dataSourceProperty, Properties properties) {
            List<Filter> proxyFilters = new ArrayList<>(2);
            String filters = properties.getProperty("druid.filters");
            if (!StringUtils.isEmpty(filters)) {
                if (filters.contains("stat")) {
                    StatFilter statFilter = new StatFilter();
                    statFilter.configFromProperties(properties);
                    proxyFilters.add(statFilter);
                }
                if (filters.contains("wall")) {
                    WallConfig wallConfig = DruidWallConfigUtil.toWallConfig(dataSourceProperty.getDruid().getWall(), gConfig.getWall());
                    WallFilter wallFilter = new WallFilter();
                    wallFilter.setConfig(wallConfig);
                    proxyFilters.add(wallFilter);
                }
                if (filters.contains("slf4j")) {
                    Slf4jLogFilter slf4jLogFilter = new Slf4jLogFilter();
                    // 由于properties上面被用了,LogFilter不能使用configFromProperties方法,这里只能一个个set了。
                    DruidSlf4jConfig slf4jConfig = gConfig.getSlf4j();
                    slf4jLogFilter.setStatementLogEnabled(slf4jConfig.getEnable());
                    slf4jLogFilter.setStatementExecutableSqlLogEnable(slf4jConfig.getStatementExecutableSqlLogEnable());
                    proxyFilters.add(slf4jLogFilter);
                }
            }
            if (this.applicationContext != null) {
                for (String filterId : gConfig.getProxyFilters()) {
                    proxyFilters.add(this.applicationContext.getBean(filterId, Filter.class));
                }
            }
            return proxyFilters;
        }
    
        private void setParam(DruidDataSource dataSource, DruidConfig config) {
            String defaultCatalog = config.getDefaultCatalog() == null ? gConfig.getDefaultCatalog() : config.getDefaultCatalog();
            if (defaultCatalog != null) {
                dataSource.setDefaultCatalog(defaultCatalog);
            }
            Boolean defaultAutoCommit = config.getDefaultAutoCommit() == null ? gConfig.getDefaultAutoCommit() : config.getDefaultAutoCommit();
            if (defaultAutoCommit != null && !defaultAutoCommit) {
                dataSource.setDefaultAutoCommit(false);
            }
            Boolean defaultReadOnly = config.getDefaultReadOnly() == null ? gConfig.getDefaultReadOnly() : config.getDefaultReadOnly();
            if (defaultReadOnly != null) {
                dataSource.setDefaultReadOnly(defaultReadOnly);
            }
            Integer defaultTransactionIsolation = config.getDefaultTransactionIsolation() == null ? gConfig.getDefaultTransactionIsolation() : config.getDefaultTransactionIsolation();
            if (defaultTransactionIsolation != null) {
                dataSource.setDefaultTransactionIsolation(defaultTransactionIsolation);
            }
    
            Boolean testOnReturn = config.getTestOnReturn() == null ? gConfig.getTestOnReturn() : config.getTestOnReturn();
            if (testOnReturn != null && testOnReturn) {
                dataSource.setTestOnReturn(true);
            }
            Integer validationQueryTimeout =
                    config.getValidationQueryTimeout() == null ? gConfig.getValidationQueryTimeout() : config.getValidationQueryTimeout();
            if (validationQueryTimeout != null && !validationQueryTimeout.equals(-1)) {
                dataSource.setValidationQueryTimeout(validationQueryTimeout);
            }
    
            Boolean sharePreparedStatements =
                    config.getSharePreparedStatements() == null ? gConfig.getSharePreparedStatements() : config.getSharePreparedStatements();
            if (sharePreparedStatements != null && sharePreparedStatements) {
                dataSource.setSharePreparedStatements(true);
            }
            Integer connectionErrorRetryAttempts =
                    config.getConnectionErrorRetryAttempts() == null ? gConfig.getConnectionErrorRetryAttempts()
                            : config.getConnectionErrorRetryAttempts();
            if (connectionErrorRetryAttempts != null && !connectionErrorRetryAttempts.equals(1)) {
                dataSource.setConnectionErrorRetryAttempts(connectionErrorRetryAttempts);
            }
            Boolean breakAfterAcquireFailure =
                    config.getBreakAfterAcquireFailure() == null ? gConfig.getBreakAfterAcquireFailure() : config.getBreakAfterAcquireFailure();
            if (breakAfterAcquireFailure != null && breakAfterAcquireFailure) {
                dataSource.setBreakAfterAcquireFailure(true);
            }
    
            Integer timeout = config.getRemoveAbandonedTimeoutMillis() == null ? gConfig.getRemoveAbandonedTimeoutMillis()
                    : config.getRemoveAbandonedTimeoutMillis();
            if (timeout != null) {
                dataSource.setRemoveAbandonedTimeoutMillis(timeout);
            }
    
            Boolean abandoned = config.getRemoveAbandoned() == null ? gConfig.getRemoveAbandoned() : config.getRemoveAbandoned();
            if (abandoned != null) {
                dataSource.setRemoveAbandoned(abandoned);
            }
    
            Boolean logAbandoned = config.getLogAbandoned() == null ? gConfig.getLogAbandoned() : config.getLogAbandoned();
            if (logAbandoned != null) {
                dataSource.setLogAbandoned(logAbandoned);
            }
    
            Integer queryTimeOut = config.getQueryTimeout() == null ? gConfig.getQueryTimeout() : config.getQueryTimeout();
            if (queryTimeOut != null) {
                dataSource.setQueryTimeout(queryTimeOut);
            }
    
            Integer transactionQueryTimeout =
                    config.getTransactionQueryTimeout() == null ? gConfig.getTransactionQueryTimeout() : config.getTransactionQueryTimeout();
            if (transactionQueryTimeout != null) {
                dataSource.setTransactionQueryTimeout(transactionQueryTimeout);
            }
        }
    
        @Override
        public boolean support(DataSourceProperty dataSourceProperty) {
            Class<? extends DataSource> type = dataSourceProperty.getType();
            return (type == null && druidExists) || (type != null && DRUID_DATASOURCE.equals(type.getName()));
        }
    }
    

    要修改的地方在createDataSource方法

    5.重写源码

    在项目下新建一个包com.baomidou.dynamic.datasource.creator(与DruidDataSourceCreator的所在包一致)
    包下新建一个类DruidDataSourceCreator
    将源码的内容原封不动的复制到新建的类
    对createDataSource方法进行修改
    修改后:

     @Override
        public DataSource createDataSource(DataSourceProperty dataSourceProperty) {
            String username=dataSourceProperty.getUsername();
            String password=dataSourceProperty.getPassword();
            try {
                DesCode des = new DesCode("这是密钥");
                username= des.decrypt(username);
                password= des.decrypt(password);
            }catch (Exception e){
                e.printStackTrace();
            }
            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setUsername(username);
            dataSource.setPassword(password);
            dataSource.setUrl(dataSourceProperty.getUrl());
            dataSource.setName(dataSourceProperty.getPoolName());
    
            String driverClassName = dataSourceProperty.getDriverClassName();
            if (!StringUtils.isEmpty(driverClassName)) {
                dataSource.setDriverClassName(driverClassName);
            }
            DruidConfig config = dataSourceProperty.getDruid();
            Properties properties = config.toProperties(gConfig);
    
            List<Filter> proxyFilters = this.initFilters(dataSourceProperty, properties);
            dataSource.setProxyFilters(proxyFilters);
    
            dataSource.configFromPropety(properties);
            //连接参数单独设置
            dataSource.setConnectProperties(config.getConnectionProperties());
            //设置druid内置properties不支持的的参数
            this.setParam(dataSource, config);
    
            if (!dataSourceProperty.getLazy()) {
                try {
                    dataSource.init();
                } catch (SQLException e) {
                    throw new ErrorCreateDataSourceException("druid create error", e);
                }
            }
            return dataSource;
        }
    

    6.完成测试

    在maven先执行clean再执行install,让重写的代码编译,替换原有的代码
    启动项目,测试验证功能.

    7.其他

    使用的IDEA自带的反编译进行的源码查看
    建议进行源码下载,这样可以看到作者的注释,变量名也会更准确
    非多数据源的加密:

    /*
     * Copyright (C) 2013, 2014 Brett Wooldridge
     *
     * 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.zaxxer.hikari.util;
    
    import java.io.PrintWriter;
    import java.sql.Connection;
    import java.sql.Driver;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.SQLFeatureNotSupportedException;
    import java.util.Enumeration;
    import java.util.Map.Entry;
    import java.util.Properties;
    
    import javax.sql.DataSource;
    
    import com.xxl.job.admin.util.DesCode;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public final class DriverDataSource implements DataSource
    {
        private static final Logger LOGGER = LoggerFactory.getLogger(DriverDataSource.class);
        private static final String PASSWORD = "password";
        private static final String USER = "user";
    
        private final String jdbcUrl;
        private final Properties driverProperties;
        private Driver driver;
    
        public DriverDataSource(String jdbcUrl, String driverClassName, Properties properties, String username, String password)
        {
            this.jdbcUrl = jdbcUrl;
            this.driverProperties = new Properties();
    
            for (Entry<Object, Object> entry : properties.entrySet()) {
                driverProperties.setProperty(entry.getKey().toString(), entry.getValue().toString());
            }
    
            if (username != null) {
                driverProperties.put(USER, driverProperties.getProperty("user", username));
            }
            if (password != null) {
                driverProperties.put(PASSWORD, driverProperties.getProperty("password", password));
            }
    
            if (driverClassName != null) {
                Enumeration<Driver> drivers = DriverManager.getDrivers();
                while (drivers.hasMoreElements()) {
                    Driver d = drivers.nextElement();
                    if (d.getClass().getName().equals(driverClassName)) {
                        driver = d;
                        break;
                    }
                }
    
                if (driver == null) {
                    LOGGER.warn("Registered driver with driverClassName={} was not found, trying direct instantiation.", driverClassName);
                    Class<?> driverClass = null;
                    ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader();
                    try {
                        if (threadContextClassLoader != null) {
                            try {
                                driverClass = threadContextClassLoader.loadClass(driverClassName);
                                LOGGER.debug("Driver class {} found in Thread context class loader {}", driverClassName, threadContextClassLoader);
                            }
                            catch (ClassNotFoundException e) {
                                LOGGER.debug("Driver class {} not found in Thread context class loader {}, trying classloader {}",
                                        driverClassName, threadContextClassLoader, this.getClass().getClassLoader());
                            }
                        }
    
                        if (driverClass == null) {
                            driverClass = this.getClass().getClassLoader().loadClass(driverClassName);
                            LOGGER.debug("Driver class {} found in the HikariConfig class classloader {}", driverClassName, this.getClass().getClassLoader());
                        }
                    } catch (ClassNotFoundException e) {
                        LOGGER.debug("Failed to load driver class {} from HikariConfig class classloader {}", driverClassName, this.getClass().getClassLoader());
                    }
    
                    if (driverClass != null) {
                        try {
                            driver = (Driver) driverClass.newInstance();
                        } catch (Exception e) {
                            LOGGER.warn("Failed to create instance of driver class {}, trying jdbcUrl resolution", driverClassName, e);
                        }
                    }
                }
            }
    
            final String sanitizedUrl = jdbcUrl.replaceAll("([?&;]password=)[^&#;]*(.*)", "$1<masked>$2");
            try {
                if (driver == null) {
                    driver = DriverManager.getDriver(jdbcUrl);
                    LOGGER.debug("Loaded driver with class name {} for jdbcUrl={}", driver.getClass().getName(), sanitizedUrl);
                }
                else if (!driver.acceptsURL(jdbcUrl)) {
                    throw new RuntimeException("Driver " + driverClassName + " claims to not accept jdbcUrl, " + sanitizedUrl);
                }
            }
            catch (SQLException e) {
                throw new RuntimeException("Failed to get driver instance for jdbcUrl=" + sanitizedUrl, e);
            }
        }
    
        @Override
        public Connection getConnection() throws SQLException
        {
            return driver.connect(jdbcUrl, driverProperties);
        }
    
        @Override
        public Connection getConnection(final String username, final String password) throws SQLException
        {
            String u=username;
            String p=password;
            try {
                DesCode des = new DesCode("这是密钥");
                u= des.decrypt(username);
                p= des.decrypt(password);
            }catch (Exception e){
                e.printStackTrace();
            }
    
            final Properties cloned = (Properties) driverProperties.clone();
            if (username != null) {
                cloned.put("user", u);
                if (cloned.containsKey("username")) {
                    cloned.put("username", u);
                }
            }
            if (password != null) {
                cloned.put("password", p);
            }
    
            return driver.connect(jdbcUrl, cloned);
        }
    
        @Override
        public PrintWriter getLogWriter() throws SQLException
        {
            throw new SQLFeatureNotSupportedException();
        }
    
        @Override
        public void setLogWriter(PrintWriter logWriter) throws SQLException
        {
            throw new SQLFeatureNotSupportedException();
        }
    
        @Override
        public void setLoginTimeout(int seconds) throws SQLException
        {
            DriverManager.setLoginTimeout(seconds);
        }
    
        @Override
        public int getLoginTimeout() throws SQLException
        {
            return DriverManager.getLoginTimeout();
        }
    
        @Override
        public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException
        {
            return driver.getParentLogger();
        }
    
        @Override
        public <T> T unwrap(Class<T> iface) throws SQLException
        {
            throw new SQLFeatureNotSupportedException();
        }
    
        @Override
        public boolean isWrapperFor(Class<?> iface) throws SQLException
        {
            return false;
        }
    }
    
    

    相关文章

      网友评论

          本文标题:springboot进行数据库连接配置的自定义加密

          本文链接:https://www.haomeiwen.com/subject/vrejnltx.html