思路
com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection
方法中,通过userCallback和passwordCallback可以对用户名密码进行自定义操作,内容如下
public PhysicalConnectionInfo createPhysicalConnection() throws SQLException {
***
String user;
if (getUserCallback() != null) {
user = getUserCallback().getName();
} else {
user = getUsername();
}
String password = getPassword();
PasswordCallback passwordCallback = getPasswordCallback();
if (passwordCallback != null) {
if (passwordCallback instanceof DruidPasswordCallback) {
DruidPasswordCallback druidPasswordCallback = (DruidPasswordCallback) passwordCallback;
druidPasswordCallback.setUrl(url);
druidPasswordCallback.setProperties(connectProperties);
}
char[] chars = passwordCallback.getPassword();
if (chars != null) {
password = new String(chars);
}
}
***
}
userCallback和passwordCallback分别为
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
protected volatile PasswordCallback passwordCallback;
protected volatile NameCallback userCallback;
public PasswordCallback getPasswordCallback() {
return passwordCallback;
}
public void setPasswordCallback(PasswordCallback passwordCallback) {
this.passwordCallback = passwordCallback;
}
public void setPasswordCallbackClassName(String passwordCallbackClassName) throws Exception {
Class<?> clazz = Utils.loadClass(passwordCallbackClassName);
if (clazz != null) {
this.passwordCallback = (PasswordCallback) clazz.newInstance();
} else {
LOG.error("load passwordCallback error : " + passwordCallbackClassName);
this.passwordCallback = null;
}
}
public NameCallback getUserCallback() {
return userCallback;
}
public void setUserCallback(NameCallback userCallback) {
this.userCallback = userCallback;
}
直接引用druid情况
如果是直接引用druid而非autoconfig形式(即非druid-spring-boot-starter形式引入)
可以直接继承
om.alibaba.druid.pool.DruidDataSource
直接重写两个对两个callback进行赋值即可
spring.datasource.type=你自己实现的类
xml配置的情况
可以配置
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
***
<property name="userCallback" ref="dbUserCallback"/>
<property name="passwordCallback" ref="dbPasswordCallback"/>
***
</bean>
本文重点:application.yml的情况
因为yml无法引用bean或对象(本人没有找到对应方法,如果有大神知道,请告知),且autoconfig是通过druiddatasourcewrapper来注入参数的。并且druid只提供了passwordCallback的className setter方法,因此只能做到对password的自定义加密解密,配置方法如下:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
passwordCallbackClassName: 你自定义的类全限定名
想要同时加密用户名密码就要用到filter代码入下:
application.yml
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
# 略过
publicKey: aabbccdd
db1: #数据源1
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/1
username: admin
password: admin
connection-properties: publicKey=${spring.datasource.druid.publicKey};username=${spring.datasource.druid.db1.username};password=${spring.datasource.druid.db1.password}
db2: #数据源2
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/2
username: admin
password: admin
connection-properties: publicKey=${spring.datasource.druid.publicKey};username=${spring.datasource.druid.db2.username};password=${spring.datasource.druid.db2.password}
db3: #数据源3
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/3
username: admin
password: admin
connection-properties: publicKey=${spring.datasource.druid.publicKey};username=${spring.datasource.druid.db3.username};password=${spring.datasource.druid.db3.password}
db4: #数据源4 数栈的tidb库,统计指标用
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/4
username: admin
password: admin
connection-properties: publicKey=${spring.datasource.druid.publicKey};username=${spring.datasource.druid.db4.username};password=${spring.datasource.druid.db4.password}
filter
import com.alibaba.druid.filter.FilterEventAdapter;
@Component
public class DataSourceFilter extends FilterEventAdapter {
@Override
public void init(DataSourceProxy dataSourceProxy) {
DruidDataSource dataSource = (DruidDataSource) dataSourceProxy;
dataSource.setUserCallback(new MyDruidUsernameCallback(dataSource.getConnectProperties()));
dataSource.setPasswordCallback(new MyDruidPasswordCallback());
super.init(dataSource);
}
}
MyDruidPasswordCallback
import com.alibaba.druid.util.DruidPasswordCallback;
public class MyDruidPasswordCallback extends DruidPasswordCallback {
@Override
public void setProperties(Properties properties) {
super.setProperties(properties);
//获取application.yml 里面配置的密码和公钥
String password = (String) properties.get("password");
String publicKey = (String) properties.get("publicKey");
if (password == null) return;
try {
// SMHelper 为自己的解密工具
String decryptPassword = SMHelper.sm4Decrypt(publicKey, password);
setPassword(decryptPassword.toCharArray());
} catch (Exception e) {
log.error("Druid ConfigTools.decrypt", e);
}
}
}
MyDruidUsernameCallback
import javax.security.auth.callback.NameCallback;
public class MyDruidUsernameCallback extends NameCallback {
private final Properties properties;
public MyDruidUsernameCallback(Properties properties) {
super("no uses, i write aimlessly");
this.properties = properties;
}
@Override
public String getName() {
String username = (String) properties.get("username");
String publicKey = (String) properties.get("publicKey");
try {
// SMHelper 为自己的解密工具
String decryptUserName = SMHelper.sm4Decrypt(publicKey, username);
super.setName(decryptUserName);
} catch (Exception e) {
log.error("Druid ConfigTools.decrypt", e);
}
return super.getName();
}
}
备注
如果可以修改druid源码的话,github上有老哥给出了其他解决方案:
支持自定义password-callback & add test case by JoeyBling · Pull Request #3877 · alibaba/druid · GitHub