DruidPasswordCallback使用方法及流程分析
功能介绍:
DruidPasswordCallback是druid提供的用于数据库加密的类,原因:直接将数据库密码写在配置文件中增加了数据库秘密泄露的风险。
配置方式介绍:
- 配置连接池
<!-- 自定义的设置数据库密码类 -->
<bean id = "dbPasswordCallback" class="cn.andrew.until.DBPasswordCallback"/>
<!-- mysql数据源配置 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="${mysql.url}" />
<property name="driverClassName" value="${mysql.driverClassName}"/>
<property name="username" value="${mysql.username}" />
<!-- 这一行可以不需要了-->
<!--<property name="password" value="${mysql.password}" />-->
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1" />
<property name="minIdle" value="1" />
<property name="maxActive" value="20" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="stat" />
<!-- 配置文件中的password应该是加密后的密码-->
<property name="connectionProperties" value="password=${mysql.password}"/>
<property name="passwordCallback" ref="dbPasswordCallback"/>
</bean>
需要注意的是 dbPasswordCallback bean配置以及DataSource bean的password,connectionProperties,passwordCallback三个属性。
2. 生成密钥对(我这使用RSA算法加密,如果使用其他算法可以跳过这一步):
public class KeyPairGenUtil {
//指定加密算法为RSA
private static final String ALGORITHM = "RSA";
//密钥长度,用来初始化
private static final int KEYSIZE = 1024;
//指定公钥存放文件
private static String PUBLIC_KEY_FILE = "c://test/pub.k";
//指定私钥存放文件
private static String PRIVATE_KEY_FILE = "c://test/pri.k";
public static void main(String[] args) throws Exception {
generateKeyPair();
encrypt("root")
}
/**
* 生成密钥对,druid中的ConfigTools类中也有生成方法可以直接调用
* @throws Exception
*/
private static void generateKeyPair() throws Exception {
// 为RSA算法创建一个KeyPairGenerator对象
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
keyPairGenerator.initialize(KEYSIZE);
//生成密匙对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 得到公钥
Key publicKey = keyPair.getPublic();
// 得到私钥
Key privateKey = keyPair.getPrivate();
FileOutputStream fos1 = null;
FileOutputStream fos2 = null;
try {
//用文件流将生成的密钥写入文件
fos1 = new FileOutputStream(PUBLIC_KEY_FILE);
fos2 = new FileOutputStream(PRIVATE_KEY_FILE);
fos1.write(Base64.byteArrayToBase64(publicKey.getEncoded()).getBytes());
fos2.write(Base64.byteArrayToBase64(privateKey.getEncoded()).getBytes());
} catch (Exception e) {
throw e;
} finally {
//清空缓存,关闭文件输出流
fos1.close();
fos2.close();
}
}
private String encrypt(String text) throws Exception {
//读取私钥
FileInputStream in2 = new FileInputStream(new File(PRIVATE_KEY_FILE));
//如果改变key的长度这里需要适当调整
byte[] buffer = new byte[2048];
int count = in2.read(buffer);
String privateKey = new String(buffer,0,count);
//加密
String code = ConfigTools.encrypt(privateKey,text);
System.out.println(code);
return code;
}
}
- DBPasswordCallback类具体实现:
/**
*需要继承DruidPasswordCallback 并重写setProperties 方法
**/
public class DBPasswordCallback extends DruidPasswordCallback {
//我使用的是RSA加密算法,所以讲公钥放在这的
private final String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsolr7//J3YqPkYGvkzL/+zoBbIDbKvYd1zm/mgHHKCHdP1oKA9L9BzfoRW6u3bf7lmUidS43eO0n7x34ThGY22NT5orwkFQ4+MuuSZve5XH+OxXVnLdWYw7WVqI0mCS52cNfQWBWjUNYAPGA+Zy0V8FHhlutBzNrb/b0YE2fCsQIDAQAB";
@Override
public void setProperties(Properties properties) {
super.setProperties(properties);
//获取配置文件中加密后的密码,和xml中的connectionProperties属性配置相关
String password = (String) properties.get("password");
try {
//解密过程,ConfigTools为druid自带,提供一些好用的函数
String dbpassword= ConfigTools.decrypt(publicKey,password);
//设置密码
setPassword(dbpassword.toCharArray());
} catch (Exception e) {
e.printStackTrace();
}
}
}
到这里就算配置成功了。如果你想知道为什么这么配置下面有一个简单的流程分析。
核心流程分析
主要代码在 DruidDataSource类的父类DruidAbstractDataSource类的createPhysicalConnection函数中核心代码:
//获取密码,按照上面的配置这里的返回值是null
String password = getPassword();
//返回我们知己定义的PasswordCallback 即DBPasswordCallback
PasswordCallback passwordCallback = getPasswordCallback();
if (passwordCallback != null) {
//这里就是DBPasswordCallback继承自DruidPasswordCallback的原因
//结合这些代码代码你可以想到去继承DruidPasswordCallback的父类PasswordCallback 但这样需要去重定义他的构造函数
if (passwordCallback instanceof DruidPasswordCallback) {
DruidPasswordCallback druidPasswordCallback = (DruidPasswordCallback) passwordCallback;
druidPasswordCallback.setUrl(url);
//数据源中的connectionProperties,选择重写DruidPasswordCallback setProperties方法的原因
druidPasswordCallback.setProperties(connectProperties);
}
//获得在DBPasswordCallback中调用set方法设置的密码
char[] chars = passwordCallback.getPassword();
if (chars != null) {
password = new String(chars);
}
}