问题背景:
快逸报表应用在tomcat应用服务器进行部署时,如果需要调用tomcat配置好的数据库连接池,就不得不把报表数据源连接的密码以明文形式暴露,这样数据库连接的用户名密码都非常容易被获取,是非常不安全的。本文将介绍如何对tomcat数据库连接池配置文件中的密码进行加密处理。
问题解决思路:
将配置文件用户相关的信息(例如:密码)进行加密使其以密文形式存在,进行初始化连接池的时候进行解密操作,达到成功创建连接池的目的。Tomcat默认使用DBCP连接池(基于common-pool的一种连接池实现),可在http://jakarta.apache.org/commons/dbcp/下载commons-dbcp源码包commons-dbcp-1.4-src.zip,对org.apache.commons.dbcp.BasicDataSourceFactory类修改,把数据库密码字段(加密后的密文)用解密程序解密,获得解密后的明文即可。
具体实现:
1. 修改org.apache.commons.dbcp.BasicDataSourceFactory类文件
找到数据源密码设置部分
value = properties.getProperty(PROP_PASSWORD);
if (value != null) {
dataSource.setPassword(value);
}
修改为:
value = properties.getProperty(PROP_PASSWORD);
if (value != null) {
dataSource.setPassword(Encode.decode(value));
}
将配置文件中的“密码”(加密后的结果)取出,调用加解密类中的解密方法Encode.decode(value)进行解密。
2. 加密类Encode.java,本例中使用加密解密模块比较简单只是用来说明问题,密文为明文的十六进制串。
public class Encode {
//编码-普通字符串转为十六进制字符串
public static String encode(String password){
String result = “”;
byte[] psd = password.getBytes();
for(int i=0;i<psd.length;i++){
result += Integer.toHexString(psd[i]&0xff);
}
return result;
}
//解码–十六进制字符串转为普通字符串
public static String decode(String password){
String result = “”;
password = password.toUpperCase();
int length = password.length() / 2;
char[] hexChars = password.toCharArray();
byte[] d = new byte[length];
for (int i = 0; i < length; i++) {
int pos = i * 2;
d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
}
result = new String(d);
return result;
}
//字符转字节
public static byte charToByte(char c) {
return (byte) “0123456789ABCDEF”.indexOf(c);
}
}
3. 数据库连接池文件,红色字体为数据源配置中密码设置,此时已经改为密文形式。
<?xml version=’1.0′ encoding=’utf-8′?>
<Context docBase=”reportmis” path=”/reportmis” privileged=”true” workDir=”work\Catalina\localhost\reportmis”>
<Resource auth=”Container” name=”mis2datasource” type=”javax.sql.DataSource”/>
<ResourceParams name=”mis2datasource”>
<parameter>
<name>password</name>
<value>696e65743231</value>
</parameter>
<parameter>
<name>url</name>
<value>jdbc:oracle:thin:@127.0.0.1:1521:orcl</value>
</parameter>
<parameter>
<name>driverClassName</name>
<value>oracle.jdbc.driver.OracleDriver</value>
</parameter>
<parameter>
<name>username</name>
<value>wanfang</value>
</parameter>
</ResourceParams>
</Context>
4. 将修改后的BasicDataSourceFactory.java和新添加的Encode.java编译后的class类文件重新打包进commons-dbcp-1.4.jar,将该包拷贝进tomcat下的common/lib目录中,重启tomcat。此时tomcat下部署的应用在连接数据源的时候都可以在不暴露密码明文的情况下进行连接。
问题补充:
1. 本例中用仅使用简单的加解密算法进行密文和明文的转化,可根据具体情况选择不同的加解密模块如MD5,base64,DES等。
2. 本例的实现中所有的连接池初始化操作均采用相同的加密解密方法,tomcat之下部署的应用可能是多个,强迫使用同一种方式不符合实际情形。可能出现以下场景:A_1项目利用B_1加解密模块,A_2项目利用B_2加解密模块,A_3项目不利用加解密模块等等,以上方式明显无法处理。BasicDataSourceFactory.java实现了接口javax.naming.spi.ObjectFactory,我们也实现该接口初始内容保持和BasicDataSourceFactory.java相同即可。只要可以在配置文件中控制加载当前配置文件类,即可达到加解密的目的。查阅JNDI设置相关知识发现Resource中有个参数factory,可以指定加载当前配置文件的类,若没有指定则默认采用BasicDataSourceFactory.java。也就是我们在设置不同数据库连接池时可以指定Resource的factory为不同,这样就可以实现不同应用采用不同的加密解密模块。
补充:
一、说明
Tomcat配置JNDI数据源时密码是以明文的形式出现在配置文件中,这样存在安全隐患,比较可取的方法是将配置文件用户相关的信息(例如:用户名、密码)进行加密使其以密文形式存在,进行初始化连接池的时候进行解密操作,达到成功创建连接池的目的。(附录是对tomcat加载过程的说明,若对其不明白请先阅读)
二、加密实现方式
第一种方式比较简单,直接修改BasicDataSourceFactory.java中读取加密字段部分
setPassword(value)----value是直接从配置文件中取到字符串值,此处加入解密方法获取明文即可达到目的。该方式优点是简单(网上相关介绍比较多),缺点也比较明显,适合于个人项目的实践练习,不适合与实际运行项目。修改BasicDataSourceFactory.java之后,所有的连接池初始化操作均采用相同的加密解密接口,tomcat之下部署的工程可能是多个公司,或者是一个公司不同时期的产品,强迫使用同一种方式不符合实际情形。可能出现以下场景:A_1项目利用B_1加解密模块,A_2项目利用B_2加解密模块,A_3项目不利用加解密模块等等,以上方式明显无法处理,所以其适用范围比较窄。
第二种方式比较专业,前提需要了解dbcp中的工厂模式。分析源代码可知BasicDataSourceFactory.java实现了接口javax.naming.spi.ObjectFactory,我们也实现该接口初始内容保持和BasicDataSourceFactory.java相同即可,之后修改参照第一种方式。只要可以在配置文件中控制加载当前配置文件类,即可达到加解密的目的。查阅JNDI设置相关知识发现Resource中有个参数factory,可以指定加载当前配置文件的类,若没有指定则默认采用BasicDataSourceFactory.java。
三、完整实例
(加密解密模块比较简单只是用来说明问题,密文为明文的十六进制串)
1>新建类HTBasicDataSourceFactory.java,内容与BasicDataSourceFactory.java完全一致。
2>HTBasicDataSourceFactory.java末尾添加如下内容
//编码-普通字符串转为十六进制字符串
public static String encode(String password){
String result = "";
byte[] psd = password.getBytes();
for(int i=0;i<psd.length;i++){
result += Integer.toHexString(psd[i]&0xff);
}
return result;
}
//解码--十六进制字符串转为普通字符串
public static String decode(String password){
String result = "";
password = password.toUpperCase();
int length = password.length() / 2;
char[] hexChars = password.toCharArray();
byte[] d = new byte[length];
for (int i = 0; i < length; i++) {
int pos = i * 2;
d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
}
result = new String(d);
return result;
}
//字符转字节
public static byte charToByte(char c) {
return (byte) "0123456789ABCDEF".indexOf(c);
}
4>打包为tomcat_dbcp_encrypt.jar,复制到Tomcat/lib目录,重新启动tomcat测试通过
附录:
Tomcat连接池采用DBCP(基于common-pool的一种连接池实现),上文就是修改dbcp源代码,BasicDataSourceFactory.java类包为org.apache.tomcat.dbcp.dbcp。下载DBCP源码之后需要确保包路径和tomcat/lib/tomcat_dbcp.jar中的一致(查看包路径方法:将jar包复制到WEB工程的lib中,)
下载DBCP源码,将common_pool.jar导入工程,还会提示工程报错,原因是缺少jta.jar。