在我们开发当中,经常会用到spring框架来读取属性文件的属性值,然后使用占位符引用属性文件的属性值来简化配置以及使配置具有更高的灵活性和通用性。
如下面的属性配置文件:db.properties
#数据库配置
db.driver=org.postgresql.Driver
db.url=jdbc\:postgresql\://10.166.176.127\:5432/test
db.username=ivsadmin
db.password=123456
db.name=ivs
对于一些敏感的属性值,例如:密码属性。为了达到安全目的,我们一般会将密码进行加密。
可能希望用户看到db.properties是这样的:
#数据库配置
db.driver=org.postgresql.Driver
db.url=jdbc\:postgresql\://10.166.176.127\:5432/ivs
db.username=ivsadmin
db.password={SMC}sYNzVKgIhOprkdGhCyt81w==
db.name=ivs
这里可以看到密码属性值是加密过的,其它的属性值不变,这样就达到安全目的。这里采用的是java的3DES加密,在前面的文章中 3DES加密、解密工具类 已经有介绍了
下面开始分析下我们的需求:
在Spring中担负对外在化应用参数的配置的是PropertyPlaceholderConfigurer和PropertyOverrideConfigurer对象,PropertyPlaceholderConfigurer实现了BeanFactoryPostProcessor接口,它能够对<bean/>中的属性值进行外在化管理。
就像这样:
为简化PropertyPlaceholderConfigurer的使用,Spring提供了<context:property-placeholder/>元素,像applicationContext.xml文件中这样:<context:property-placeholder location="classpath:db.properties" />
这里就很清楚了,我们只要继承PropertyPlaceholderConfigurer对象,重写PropertiesLoaderSupport接口的loadProperties方法,就可以对外部属性文件的属性值进行相关的操作了
明白了需求,下来开始我们的实现代码:
DecryptPropertyPlaceholderConfigurer.java
其中propertiesPersister变量用我们写的DefaultPropertiesPersister类来实现,DecryptPropertiesPersister.java对象
最后需要修改下applicationContext.xml文件,如下:
这样对属性的加密就完成了,Spring进行加载的完成后,属性就加密了
提示:如果在配置中有多个配置文件需要加载,并且这些属性文件不需要做任何处理,那就需要添加下面的配置:
如下面的属性配置文件:db.properties
#数据库配置
db.driver=org.postgresql.Driver
db.url=jdbc\:postgresql\://10.166.176.127\:5432/test
db.username=ivsadmin
db.password=123456
db.name=ivs
applicationContext.xml文件
<context:property-placeholder location="classpath:db.properties" />
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="${db.driver}" />
<property name="jdbcUrl" value="${db.url}" />
<property name="user" value="${db.username}" />
<property name="password" value="${db.password}" />
<property name="checkoutTimeout" value="3000" />
</bean>
对于一些敏感的属性值,例如:密码属性。为了达到安全目的,我们一般会将密码进行加密。
可能希望用户看到db.properties是这样的:
#数据库配置
db.driver=org.postgresql.Driver
db.url=jdbc\:postgresql\://10.166.176.127\:5432/ivs
db.username=ivsadmin
db.password={SMC}sYNzVKgIhOprkdGhCyt81w==
db.name=ivs
这里可以看到密码属性值是加密过的,其它的属性值不变,这样就达到安全目的。这里采用的是java的3DES加密,在前面的文章中 3DES加密、解密工具类 已经有介绍了
下面开始分析下我们的需求:
在Spring中担负对外在化应用参数的配置的是PropertyPlaceholderConfigurer和PropertyOverrideConfigurer对象,PropertyPlaceholderConfigurer实现了BeanFactoryPostProcessor接口,它能够对<bean/>中的属性值进行外在化管理。
就像这样:
<bean id="propertyConfigurer1" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<value>WEB-INF/classes/db.properties</value>
</property>
</bean>
为简化PropertyPlaceholderConfigurer的使用,Spring提供了<context:property-placeholder/>元素,像applicationContext.xml文件中这样:<context:property-placeholder location="classpath:db.properties" />
这里就很清楚了,我们只要继承PropertyPlaceholderConfigurer对象,重写PropertiesLoaderSupport接口的loadProperties方法,就可以对外部属性文件的属性值进行相关的操作了
明白了需求,下来开始我们的实现代码:
DecryptPropertyPlaceholderConfigurer.java
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Properties;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.core.io.Resource;
import com.huawei.smc.commons.constants.CommonContants;
/**
* <一句话功能简述>
*
* @author hKF44803
* @version [版本号, 2011-12-6]
* @see [相关类/方法]
* @since [产品/模块版本]
*/
public class DecryptPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer
{
private Resource[] locations;
private DecryptPropertiesPersister propertiesPersister = new DecryptPropertiesPersister();
private String fileEncoding = "utf-8";
private boolean ignoreResourceNotFound = false;
/**
* {@inheritDoc}
*/
@Override
public void setLocations(Resource[] locations)
{
this.locations = locations;
}
/**
* {@inheritDoc}
*/
@Override
public void setFileEncoding(String encoding)
{
this.fileEncoding = encoding;
}
/**
* {@inheritDoc}
*/
@Override
public void setIgnoreResourceNotFound(boolean ignoreResourceNotFound)
{
this.ignoreResourceNotFound = ignoreResourceNotFound;
}
/**
* {@inheritDoc}
*/
@Override
public void loadProperties(Properties props)
throws IOException
{
// 属性文件是否为空
if (this.locations != null)
{
// 循环读取属性文件
for (int i = 0; i < this.locations.length; i++)
{
Resource location = this.locations[i];
InputStream is = null;
FileOutputStream fos = null;
try
{
is = location.getInputStream();
// 检查文件是否是XML文件
if (location.getFilename().endsWith(XML_FILE_EXTENSION))
{
this.propertiesPersister.loadFromXml(props, is);
}
// 属性文件
else
{
this.propertiesPersister.doLoad(props, new InputStreamReader(is, this.fileEncoding));
String content = this.propertiesPersister.getEncryptContent();
// 查找是否存在加密标识
if (StringUtils.contains(content, CommonContants.DECRYPT_FLAG))
{
try
{
File file = location.getFile();
fos = new FileOutputStream(file);
fos.write(this.propertiesPersister.getEncryptContent().getBytes());
fos.flush();
}
finally
{
if (null != fos)
{
fos.close();
}
}
}
}
}
catch (IOException ex)
{
if (this.ignoreResourceNotFound)
{
if (logger.isWarnEnabled())
{
logger.warn("Could not load properties from " + location + ": " + ex.getMessage());
}
}
else
{
throw ex;
}
}
finally
{
if (is != null)
{
is.close();
}
}
}
}
}
}
其中propertiesPersister变量用我们写的DefaultPropertiesPersister类来实现,DecryptPropertiesPersister.java对象
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Properties;
import org.springframework.util.DefaultPropertiesPersister;
import org.springframework.util.StringUtils;
import com.huawei.smc.commons.constants.CommonContants;
import com.huawei.smc.commons.constants.NumberConstants;
import com.huawei.smc.commons.util.ThreeDesUtil;
/**
* 重载DefaultPropertiesPersister类
*
* @author hKF44803
* @version [版本号, 2011-12-6]
* @see [相关类/方法]
* @since [产品/模块版本]
*/
public class DecryptPropertiesPersister extends DefaultPropertiesPersister
{
// 加密后的字符串
private String encryptContent;
public String getEncryptContent()
{
return encryptContent;
}
/**
* {@inheritDoc}
*/
@Override
protected void doLoad(Properties props, Reader reader)
throws IOException
{
BufferedReader in = new BufferedReader(reader);
// 最后写入的内容
StringBuilder sbContent = new StringBuilder();
// 循环读取文件
while (true)
{
// 读取每一行
String line = in.readLine();
// 非空检查
if (line == null)
{
break;
}
// 去掉空格
line = StringUtils.trimLeadingWhitespace(line);
// 读取行为空,跳出循环
if (line.length() == 0)
{
// 长度为0,换行
sbContent.append("\n");
continue;
}
// 每行的第一个字符
char firstChar = line.charAt(0);
// 第一个字符不是#和!
if (firstChar != '#' && firstChar != '!')
{
while (endsWithContinuationMarker(line))
{
String nextLine = in.readLine();
line = line.substring(0, line.length() - 1);
// 非空检查
if (nextLine != null)
{
line += StringUtils.trimLeadingWhitespace(nextLine);
}
}
// 查找等号所有位置的索引
int separatorIndex = line.indexOf("=");
// 没有等号
if (separatorIndex == -1)
{
separatorIndex = line.indexOf(":");
}
// 取KEY
String key = (separatorIndex != -1) ? line.substring(0, separatorIndex) : line;
// 取KEY的值
String value = (separatorIndex != -1) ? line.substring(separatorIndex + 1) : "";
// 去掉空格
key = StringUtils.trimTrailingWhitespace(key);
value = StringUtils.trimLeadingWhitespace(value);
// 将所有的属性放到持久的属性集*
props.put(unescape(key), unescape(value));
// DB属性文件
if (CommonContants.DB_PASSWORD_PROPS.equals(key))
{
// 实例加密工具类
ThreeDesUtil desUtil = new ThreeDesUtil();
// DB密码解密
if (value.startsWith(CommonContants.DECRYPT_FLAG))
{
// 去掉标识
value = value.substring(NumberConstants.INT_5);
// 对加密的属性进行3DES解密
value = desUtil.decrypt(value);
// 解密的值放到props中
props.put(unescape(key), unescape(value));
}
// DB密码加密
else
{
// 加密指定的值
String strEncrypt = desUtil.encrypt(value);
// 加密后的值添加一个标识,区分解密、加密
value = CommonContants.DECRYPT_FLAG + strEncrypt;
// 加密后的行
line = key + CommonContants.PROPERTIES_SEPERATE + value;
sbContent.append(line + "\n");
}
}
// 追加其它的属性
else
{
sbContent.append(line + "\n");
}
}
else
{
// 追加读取的注释内容
sbContent.append(line + "\n");
}
}
encryptContent = sbContent.toString();
}
}
最后需要修改下applicationContext.xml文件,如下:
<bean id="propertyConfigurer1" class="com.huawei.smc.commons.DecryptPropertyPlaceholderConfigurer">
<property name="locations">
<value>WEB-INF/classes/db.properties</value>
</property>
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${db.driver}" />
<property name="jdbcUrl" value="${db.url}" />
<property name="user" value="${db.username}" />
<property name="password" value="${db.password}" />
<property name="checkoutTimeout" value="3000" />
</bean>
这样对属性的加密就完成了,Spring进行加载的完成后,属性就加密了
提示:如果在配置中有多个配置文件需要加载,并且这些属性文件不需要做任何处理,那就需要添加下面的配置:
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="order" value="1" />
<property name="ignoreUnresolvablePlaceholders" value="true" />
<property name="locations">
<value>WEB-INF/classes/smc.properties</value>
</property>