經(jīng)驗(yàn)備忘,僅供參考
1.場景
配置文件的數(shù)據(jù)庫連接的用戶名和密碼需要改成密文,提高安全性.
加解密使用一個工具類來實(shí)現(xiàn),并非是泛用的加解密過程,不方便用插件.
2.思路
1.找到從配置文件獲取用戶名和密碼的邏輯,或者使用用戶名和密碼創(chuàng)建數(shù)據(jù)源的邏輯
2.重寫對應(yīng)邏輯的源碼類,增加解密的步驟
3.項目環(huán)境
Spring Boot 2.4.2
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- 動態(tài)數(shù)據(jù)源 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
</dependency>
<!-- 阿里數(shù)據(jù)庫連接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- postgresql驅(qū)動包 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
項目實(shí)際使用了多數(shù)據(jù)源功能
#默認(rèn)數(shù)據(jù)源
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.確認(rèn)創(chuàng)建數(shù)據(jù)源邏輯
將數(shù)據(jù)庫連接改成錯誤的
查看異常報錯信息,進(jìn)入報錯的類
報錯節(jié)點(diǎn)增加斷點(diǎn),debug進(jìn)入斷點(diǎn),由斷點(diǎn)向調(diào)用鏈的上方回溯
在疑似地方繼續(xù)增加斷點(diǎn),反復(fù)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數(shù)據(jù)源創(chuàng)建器
*
* @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);
//連接參數(shù)單獨(dú)設(shè)置
dataSource.setConnectProperties(config.getConnectionProperties());
//設(shè)置druid內(nèi)置properties不支持的的參數(shù)
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.重寫源碼
在項目下新建一個包c(diǎn)om.baomidou.dynamic.datasource.creator(與DruidDataSourceCreator的所在包一致)
包下新建一個類DruidDataSourceCreator
將源碼的內(nèi)容原封不動的復(fù)制到新建的類
對createDataSource方法進(jìn)行修改
修改后:
@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);
//連接參數(shù)單獨(dú)設(shè)置
dataSource.setConnectProperties(config.getConnectionProperties());
//設(shè)置druid內(nèi)置properties不支持的的參數(shù)
this.setParam(dataSource, config);
if (!dataSourceProperty.getLazy()) {
try {
dataSource.init();
} catch (SQLException e) {
throw new ErrorCreateDataSourceException("druid create error", e);
}
}
return dataSource;
}
6.完成測試
在maven先執(zhí)行clean再執(zhí)行install,讓重寫的代碼編譯,替換原有的代碼
啟動項目,測試驗(yàn)證功能.
7.其他
使用的IDEA自帶的反編譯進(jìn)行的源碼查看
建議進(jìn)行源碼下載,這樣可以看到作者的注釋,變量名也會更準(zhǔn)確
非多數(shù)據(jù)源的加密:
/*
* 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;
}
}