转载:http://my.oschina.net/noahxiao/blog/163719
一、背景
处于安全考虑需要对.properties中的数据库用户名与密码等敏感数据进行加密。项目中使用了Spring3框架统一加载属性文件,所以最好可以干扰这个加载过程来实现对.properties文件中的部分属性进行加密。
属性文件中的属性最初始时敏感属性值可以为明文,程序第一次执行后自动加密明文为密文。
二、问题分析
-
扩展PropertyPlaceholderConfigurer最好的方式就是编写一个继承该类的子类。
- 外部设置locations时,记录全部locations信息,为加密文件保留属性文件列表。重写setLocations与setLocation方法(在父类中locations私有)
- 寻找一个读取属性文件属性的环节,检测敏感属性加密情况。对有已有加密特征的敏感属性进行解密。重写convertProperty方法来实现。
- 属性文件第一次加载完毕后,立即对属性文件中的明文信息进行加密。重写postProcessBeanFactory方式来实现。
三、程序开发
1、目录结构
注:aes包中为AES加密工具类,可以根据加密习惯自行修改
2、EncryptPropertyPlaceholderConfigurer(详见注释)
001 |
package org.noahx.spring.propencrypt;
|
003 |
import org.noahx.spring.propencrypt.aes.AesUtils;
|
004 |
import org.slf4j.Logger;
|
005 |
import org.slf4j.LoggerFactory;
|
006 |
import org.springframework.beans.BeansException;
|
007 |
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
008 |
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
|
009 |
import org.springframework.core.io.Resource;
|
013 |
import java.util.regex.Matcher;
|
014 |
import java.util.regex.Pattern;
|
017 |
* Created with IntelliJ IDEA.
|
021 |
* To change this template use File | Settings | File Templates.
|
023 |
public class EncryptPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
|
025 |
private static final String SEC_KEY = "@^_^123aBcZ*" ;
|
026 |
private static final String ENCRYPTED_PREFIX = "Encrypted:{" ;
|
027 |
private static final String ENCRYPTED_SUFFIX = "}" ;
|
028 |
private static Pattern encryptedPattern = Pattern.compile( "Encrypted:\\{((\\w|\\-)*)\\}" );
|
030 |
private Logger logger = LoggerFactory.getLogger( this .getClass());
|
032 |
private Set<String> encryptedProps = Collections.emptySet();
|
034 |
public void setEncryptedProps(Set<String> encryptedProps) {
|
035 |
this .encryptedProps = encryptedProps;
|
039 |
protected String convertProperty(String propertyName, String propertyValue) {
|
041 |
if (encryptedProps.contains(propertyName)) {
|
042 |
final Matcher matcher = encryptedPattern.matcher(propertyValue);
|
043 |
if (matcher.matches()) {
|
044 |
String encryptedString = matcher.group( 1 );
|
045 |
String decryptedPropValue = AesUtils.decrypt(propertyName + SEC_KEY, encryptedString);
|
047 |
if (decryptedPropValue != null ) {
|
048 |
propertyValue = decryptedPropValue;
|
050 |
logger.error( "Decrypt " + propertyName + "=" + propertyValue + " error!" );
|
055 |
return super .convertProperty(propertyName, propertyValue);
|
059 |
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
060 |
super .postProcessBeanFactory(beanFactory);
|
062 |
for (Resource location : locations) {
|
065 |
final File file = location.getFile();
|
068 |
if (file.canWrite()) {
|
071 |
if (logger.isWarnEnabled()) {
|
072 |
logger.warn( "File '" + location + "' can not be write!" );
|
077 |
if (logger.isWarnEnabled()) {
|
078 |
logger.warn( "File '" + location + "' is not a normal file!" );
|
082 |
} catch (IOException e) {
|
083 |
if (logger.isWarnEnabled()) {
|
084 |
logger.warn( "File '" + location + "' is not a normal file!" );
|
091 |
private boolean isBlank(String str) {
|
093 |
if (str == null || (strLen = str.length()) == 0 ) {
|
096 |
for ( int i = 0 ; i < strLen; i++) {
|
097 |
if ((Character.isWhitespace(str.charAt(i)) == false )) {
|
104 |
private boolean isNotBlank(String str) {
|
105 |
return !isBlank(str);
|
114 |
private void encrypt(File file) {
|
116 |
List<String> outputLine = new ArrayList<String>();
|
118 |
boolean doEncrypt = false ;
|
121 |
BufferedReader bufferedReader = null ;
|
124 |
bufferedReader = new BufferedReader( new FileReader(file));
|
129 |
line = bufferedReader.readLine();
|
131 |
if (isNotBlank(line)) {
|
133 |
if (!line.startsWith( "#" )) {
|
134 |
String[] lineParts = line.split( "=" );
|
135 |
String key = lineParts[ 0 ];
|
136 |
String value = lineParts[ 1 ];
|
137 |
if (key != null && value != null ) {
|
138 |
if (encryptedProps.contains(key)) {
|
139 |
final Matcher matcher = encryptedPattern.matcher(value);
|
140 |
if (!matcher.matches()) {
|
142 |
value = ENCRYPTED_PREFIX + AesUtils.encrypt(key + SEC_KEY, value) + ENCRYPTED_SUFFIX;
|
144 |
line = key + "=" + value;
|
148 |
if (logger.isDebugEnabled()) {
|
149 |
logger.debug( "encrypt property:" + key);
|
156 |
outputLine.add(line);
|
159 |
} while (line != null );
|
162 |
} catch (FileNotFoundException e) {
|
163 |
logger.error(e.getMessage(), e);
|
164 |
} catch (IOException e) {
|
165 |
logger.error(e.getMessage(), e);
|
167 |
if (bufferedReader != null ) {
|
169 |
bufferedReader.close();
|
170 |
} catch (IOException e) {
|
171 |
logger.error(e.getMessage(), e);
|
177 |
BufferedWriter bufferedWriter = null ;
|
180 |
tmpFile = File.createTempFile(file.getName(), null , file.getParentFile());
|
182 |
if (logger.isDebugEnabled()) {
|
183 |
logger.debug( "Create tmp file '" + tmpFile.getAbsolutePath() + "'." );
|
186 |
bufferedWriter = new BufferedWriter( new FileWriter(tmpFile));
|
188 |
final Iterator<String> iterator = outputLine.iterator();
|
189 |
while (iterator.hasNext()) {
|
190 |
bufferedWriter.write(iterator.next());
|
191 |
if (iterator.hasNext()) {
|
192 |
bufferedWriter.newLine();
|
196 |
bufferedWriter.flush();
|
197 |
} catch (IOException e) {
|
198 |
logger.error(e.getMessage(), e);
|
200 |
if (bufferedWriter != null ) {
|
202 |
bufferedWriter.close();
|
203 |
} catch (IOException e) {
|
204 |
logger.error(e.getMessage(), e);
|
209 |
File backupFile = new File(file.getAbsoluteFile() + "_" + System.currentTimeMillis());
|
212 |
if (!file.renameTo(backupFile)) {
|
213 |
logger.error( "Could not encrypt the file '" + file.getAbsoluteFile() + "'! Backup the file failed!" );
|
216 |
if (logger.isDebugEnabled()) {
|
217 |
logger.debug( "Backup the file '" + backupFile.getAbsolutePath() + "'." );
|
220 |
if (!tmpFile.renameTo(file)) {
|
221 |
logger.error( "Could not encrypt the file '" + file.getAbsoluteFile() + "'! Rename the tmp file failed!" );
|
223 |
if (backupFile.renameTo(file)) {
|
224 |
if (logger.isInfoEnabled()) {
|
225 |
logger.info( "Restore the backup, success." );
|
228 |
logger.error( "Restore the backup, failed!" );
|
232 |
if (logger.isDebugEnabled()) {
|
233 |
logger.debug( "Rename the file '" + tmpFile.getAbsolutePath() + "' -> '" + file.getAbsoluteFile() + "'." );
|
236 |
boolean dBackup = backupFile.delete();
|
238 |
if (logger.isDebugEnabled()) {
|
239 |
logger.debug( "Delete the backup '" + backupFile.getAbsolutePath() + "'.(" + dBackup + ")" );
|
249 |
protected Resource[] locations;
|
252 |
public void setLocations(Resource[] locations) {
|
253 |
super .setLocations(locations);
|
254 |
this .locations = locations;
|
258 |
public void setLocation(Resource location) {
|
259 |
super .setLocation(location);
|
260 |
this .locations = new Resource[]{location};
|
3、spring.xml
01 |
<? xml version = "1.0" encoding = "UTF-8" ?>
|
02 |
< beans xmlns = "http://www.springframework.org/schema/beans"
|
03 |
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
|
04 |
xmlns:context = "http://www.springframework.org/schema/context"
|
05 |
xsi:schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" >
|
07 |
< context:property-placeholder location = "/WEB-INF/spring/spring.properties" />
|
10 |
< bean id = "encryptPropertyPlaceholderConfigurer"
|
11 |
class = "org.noahx.spring.propencrypt.EncryptPropertyPlaceholderConfigurer" >
|
12 |
< property name = "locations" >
|
14 |
< value >/WEB-INF/spring/spring.properties</ value >
|
17 |
< property name = "encryptedProps" >
|
19 |
< value >db.jdbc.username</ value >
|
20 |
< value >db.jdbc.password</ value >
|
21 |
< value >db.jdbc.url</ value >
|
四、运行效果
1、日志
1 |
[RMI TCP Connection(2)-127.0.0.1] DEBUG org.noahx.spring.propencrypt.EncryptPropertyPlaceholderConfigurer - encrypt property:db.jdbc.url |
2 |
[RMI TCP Connection(2)-127.0.0.1] DEBUG org.noahx.spring.propencrypt.EncryptPropertyPlaceholderConfigurer - encrypt property:db.jdbc.username |
3 |
[RMI TCP Connection(2)-127.0.0.1] DEBUG org.noahx.spring.propencrypt.EncryptPropertyPlaceholderConfigurer - encrypt property:db.jdbc.password |
4 |
[RMI TCP Connection(2)-127.0.0.1] DEBUG org.noahx.spring.propencrypt.EncryptPropertyPlaceholderConfigurer - Create tmp file '/nautilus/workspaces/idea/spring-prop-encrypt/target/spring-prop-encrypt-1.0-SNAPSHOT/WEB-INF/spring/spring.properties2420183175827237221.tmp' .
|
5 |
[RMI TCP Connection(2)-127.0.0.1] DEBUG org.noahx.spring.propencrypt.EncryptPropertyPlaceholderConfigurer - Backup the file '/nautilus/workspaces/idea/spring-prop-encrypt/target/spring-prop-encrypt-1.0-SNAPSHOT/WEB-INF/spring/spring.properties_1379959755837' .
|
6 |
[RMI TCP Connection(2)-127.0.0.1] DEBUG org.noahx.spring.propencrypt.EncryptPropertyPlaceholderConfigurer - Rename the file '/nautilus/workspaces/idea/spring-prop-encrypt/target/spring-prop-encrypt-1.0-SNAPSHOT/WEB-INF/spring/spring.properties2420183175827237221.tmp' -> '/nautilus/workspaces/idea/spring-prop-encrypt/target/spring-prop-encrypt-1.0-SNAPSHOT/WEB-INF/spring/spring.properties' .
|
7 |
[RMI TCP Connection(2)-127.0.0.1] DEBUG org.noahx.spring.propencrypt.EncryptPropertyPlaceholderConfigurer - Delete the backup '/nautilus/workspaces/idea/spring-prop-encrypt/target/spring-prop-encrypt-1.0-SNAPSHOT/WEB-INF/spring/spring.properties_1379959755837' .( true )
|
2、原属性文件
1 |
db.jdbc.driver=com.mysql.jdbc.Driver |
2 |
db.jdbc.url=jdbc:mysql://localhost:3306/noah?useUnicode= true &characterEncoding=utf8
|
3、加密后的文件
1 |
db.jdbc.driver=com.mysql.jdbc.Driver |
2 |
db.jdbc.url=Encrypted:{e5ShuhQjzDZrkqoVdaO6XNQrTqCPIWv8i_VR4zaK28BrmWS_ocagv3weYNdr0WwI} |
3 |
db.jdbc.username=Encrypted:{z5aneQi_h4mk4LEqhjZU-A} |
4 |
db.jdbc.password=Encrypted:{v09a0SrOGbw-_DxZKieu5w} |
注:因为密钥与属性名有关,所以相同值加密后的内容也不同,而且不能互换值。
五、源码下载
附件地址:http://sdrv.ms/18li77V
六、总结
在成熟加密框架中jasypt(http://www.jasypt.org/)很不错,包含了spring,hibernate等等加密。试用了一些功能后感觉并不太适合我的需要。
加密的安全性是相对的,没有绝对安全的东西。如果有人反编译了加密程序获得了加密解密算法也属正常。希望大家不要因为是否绝对安全而讨论不休。
如果追求更高级别的加密可以考虑混淆class的同时对class文件本身进行加密,改写默认的classloader加载加密class(调用本地核心加密程序,非Java)。
分享到:
相关推荐
Spring默认的`PropertyPlaceholderConfigurer`不直接支持加密的属性文件,但它提供了扩展点,允许我们自定义实现来处理加密后的属性。以下是一种实现方式: 1. 创建一个自定义的`PropertyPlaceholderConfigurer`...
在Spring框架中,属性占位符`PropertyPlaceholderConfigurer`是一个重要的工具,用于处理配置文件中的属性值引用。它使得我们可以在XML配置文件中使用占位符`${...}`来引用外部属性文件中的值,从而使应用配置更加...
PropertyPlaceholderConfigurer 不仅可以用于加载 Properties 文件,还可以用于实现其他功能,例如属性文件加密解密等。在后续的文章中,我们将继续讨论这些扩展应用。 PropertyPlaceholderConfigurer 是 Spring ...
在Spring中,`PropertyPlaceholderConfigurer`是一个非常重要的类,它用于处理属性文件中的占位符,将它们替换为实际的值。这在配置管理中起到了关键作用,特别是在大型分布式系统中,动态配置管理变得尤为重要。...
在上面的配置中,我们可以通过location属性指定一个配置文件的路径,也可以通过locations属性指定多个配置文件的路径。PropertyPlaceholderConfigurer会自动读取这些配置文件,并将其转换为Properties对象。 此外,...
`propertyConfigurer`则告诉Spring使用解密器来处理配置文件中的加密属性。 最后,关于`junfeng`这个文件,它可能是作者在文章中提到的一种特定的加密方式或者一个工具的名称。由于没有具体的文件内容,这里无法给...
在本例中,`jdbc.properties`是一个属性文件,它包含了JDBC连接所需的全部或部分参数。以下是如何在Spring中从这个文件中读取这些参数的步骤: 1. **创建属性文件**: 首先,我们需要创建一个名为`jdbc.properties...
`PropertyPlaceholderConfigurer`是一个Bean定义后处理器,它会替换Bean定义中的占位符(如`${key}`)为属性文件中的值。而`@PropertySource`注解可以直接在类级别上声明,指示Spring从指定的属性文件中读取属性。 ...
本文将详细介绍如何在BBoss的IOC配置文件中引用外部属性文件,以便更好地管理和动态配置应用。 首先,BBoss的IOC配置文件通常是一个XML文件,比如`bboss-ioc.xml`,在这个文件中我们可以声明并配置各种bean。当需要...
本文将详细介绍一个基于Spring 2.5的配置文件加密方案,该方案涉及对`.properties`属性文件中的变量值进行加密,并在Spring启动时自动解密。 首先,我们需要创建一个属性文件,例如`mail.properties`,包含需要加密...
本文将详细介绍如何在Spring中对配置文件进行加密,以保护这些敏感数据。 首先,我们需要理解Spring配置文件的基本结构。一个典型的Spring配置文件(如`applicationContext.xml`)是一个XML文档,它包含Bean的定义...
这里,`PropertyPlaceholderConfigurer` 会查找指定位置(例如 `config.properties`)的属性文件,并将其中的键值对与XML或Java配置中的占位符进行匹配替换。这使得我们的配置更加灵活,可以将一些敏感信息(如...
该类作为Spring的容器后处理器,在应用程序上下文初始化阶段自动读取指定的属性文件,并将其中的键值对映射到配置文件中的占位符上,从而完成具体的配置替换。这种机制特别适用于以下场景: - **数据库配置**:如...
- **功能**: 它能够将`properties`文件中的键值对映射到Spring容器中,供其他Bean使用。 **2. 动态加载配置文件** - **目的**: 根据部署环境的不同,动态地更改应用使用的配置文件,从而实现环境间的灵活切换。 - *...
此外,Spring Boot引入了更强大的`@ConfigurationProperties`,它能自动绑定properties文件中的键值对到bean的属性上,提供了更面向对象的配置方式。但这个特性属于Spring Boot范畴,不属于SSM的经典组合。 综上所...
在上面的配置中,我们使用 PropertyPlaceholderConfigurer 来引入外部属性文件 system.properties,然后在数据源 Bean 中使用占位符 ${} 来引用属性文件中的属性项。 PropertyPlaceholderConfigurer 属性说明 ...
例如,可以将数据库连接信息写入属性文件,如`database.properties`,然后在Spring配置文件中使用`PropertyPlaceholderConfigurer`引入属性文件。这样,我们就可以在XML配置中使用`${...}`的方式来引用属性值,如`${...
接下来,我们将在Spring的配置文件(如`applicationContext.xml`)中声明一个`PropertyPlaceholderConfigurer` bean,它负责加载并解析Properties文件。配置如下: ```xml class="org.springframework.beans....
`EncryptPropertyPlaceholderConfigurer.java`就是这样一个类,它扩展了Spring的`PropertyPlaceholderConfigurer`,并覆盖了`resolvePlaceholder`方法来处理加密的属性值。 ```java import org.springframework....