`

扩展PropertyPlaceholderConfigurer

阅读更多

扩展PropertyPlaceholderConfigurer

  要Spring配置时,将一些重要的信息独立到属性文件中是比较常见的做法,Spring只支持明文存放的属性文件,在某些场合下,我们可以希望对属性文件加密保存,以保证关键信息的安全 。通过扩展PropertyPlaceholderConfigurer,在属性文件流加载后应用前进行解密就可以很好地解决这个问题了。

在配置数据源或邮件服务器 等 资源时,你可以直接在Spring配置文件中配置用户名、密码连接地址等信息。更好的一种做法是将这些资源的配置信息独立到一个属性文件中,在 Spring的配置文件中,通过形如${user}、${password}等占位符引用属性文件的属性值,这种配置方式有两个明显的好处:
- 减少维护的工作量:资源的配置信息可以多应用共享,在多个应用使用同一资源的情况下,如果资源的地址、用户名等配置信息发生了更改,你只要调整属性文件就可以了;
- 使部署更简单:Spring配置文件主要描述应用程序中的Bean,这些配置信息在开发完成后,应该就固定下来了,在部署应用时,需要根据部署环境调整是就是数据源,邮件服务器 的配置信息,将它们的配置信息独立到属性文件中,应用部署人员只需要调整资源属性文件即可,根本不需要关注内容复杂的Spring配置文件。不仅给部署和维护带来了方便,也降低了出错的机率。 
    Spring为我们提供了一个BeanFactoryPostProcessorBean工厂后置处理器接口的实现 类:org.springframework.beans.factory.config.PropertyPlaceholderConfigurer, 它的主要功能是对引用了外部属性值的<bean>进行处理,将其翻译成真实的配置值。 
    一般的属性信息以明文的方式存放在属性文件中并没有什么问题,但如果是数据源或邮件服务器用户名密码等重要的信息,在某些场合,我们可能需要以密文的方式 保存。虽然Web应用的客户端用户看不到配置文件的,但有时,我们只希望特定的维护人员掌握重要资源的配置信息,而不是毫无保留地对所有可以进入部署机器 的用户开放。 
    对于这种具有高度安全 性要求的系统(如电信、银行、重点人口库等),我们需要对资源连接等属性配置文件中的配置信息加密存放。然后让Spring容器启动时,读入配置文件后,先进行解密,然后再进行占位符的替换。 
    很可惜,PropertyPlaceholderConfigurer只支持明文的属性文件。但是,我们可以充分利用Spring框架的扩展性,通过扩展 PropertyPlaceholderConfigurer类来达到我们的要求。本文将讲解使用加密属性文件的原理并提供具体的实现。 

    以传统的方式使用属性文件  
    一般情况下,外部属性文件用于定义诸如数据源或邮件服务器之类的配置信息。这里,我们通过一个简单的例子,讲解使用属性文件的方法。假设有一个car.properties属性文件,文件内容如下: 
    brand=红旗CA72 
    maxSpeed=250 
    price=20000.00 
    该文件放在类路径的com/baobaotao/目录下,在Spring配置文件中利用PropertyPlaceholderConfigurer引入这个配置文件,并通过占位符引用属性文件内的属性项,如代码清单 1所示:
代码清单 1 使用外部属性文件进行配置

① 引入外部属性文件
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
       <list>
          <value>classpath:com/baobaotao/car.properties</value> ② 指定属性文件地址
       </list>
    </property>
    <property name="fileEncoding" value="utf-8"/>
</bean>
③ 引用外部属性的值,对car进行配置
<bean id="car" class="com.baobaotao.place.Car">
    <property name="brand" value="${brand}" />
    <property name="maxSpeed" value="${maxSpeed}" />
    <property name="price" value="${price}" />
</bean>

  在①处,我们通过PropertyPlaceholderConfigurer这个BeanFactoryPostProcessor实现类引用外部的属 性文件,通过它的locations属性指定Spring配置文件中引用到的属性文件,在PropertyPlaceholderConfigurer内 部,locations是一个Resource数组,所以你可以在地址前添加资源类型前缀,如②处所示。如果需要引用多个属性文件,只需要在②处添加相 应<value>配置项即可。 

    分析PropertyPlaceholderConfigurer结构 
    我们知道Spring通过PropertyPlaceholderConfigurer提供对外部属性文件的支持,为了使用加密的属性文件,我们就需要分析该类的工作机理,再进行改造。所以我们先来了解一下该类的结构:

    其中PropertiesLoaderSupport类有一个重要的protected void loadProperties(Properties props)方法,查看它的注释,可以知道该方法的作用是将PropertyPlaceholderConfigurer 中locations属性所定义的属性文件的内容读取到props入参对象中。这个方法比较怪,Java很少通过入参承载返回值,但这个方法就是这样。 

    所以,我们只要简单地重载这个方法,在将资源文件的内容转换为Properties之前,添加一个解密的步骤就可以了。但 是,PropertiesLoaderSupport的设计有一个很让人遗憾的地方,它的locations属性是private的,只提供setter 没有提供getter。因此,无法在子类中获取PropertiesLoaderSupport中的locations(资源地址),所以我们得在子类重 新定义locations属性并覆盖PropertiesLoaderSupport中的setLocations()方法。

编写支持加密属性文件的实现类 
    通过以上分析,我们设计一个支持加密属性文件的增强型PropertyPlaceholderConfigurer,其代码如所示:
代码清单 2 DecryptPropertyPlaceholderConfigurer

 

package com.baobaotao;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.Key;
import java.util.Properties;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.core.io.Resource;
import org.springframework.util.DefaultPropertiesPersister;
import org.springframework.util.PropertiesPersister;
public class DecryptPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
    private Resource[] locations;
//①重新定义父类中的这个同名属性
private Resource keyLocation;
//②用于指定密钥文件
public void setKeyLocation(Resource keyLocation) {
        this.keyLocation = keyLocation;
    }
    public void setLocations(Resource[] locations) {
        this.locations = locations;
    }
    public void loadProperties(Properties props) throws IOException {
        if (this.locations != null) {
            PropertiesPersister propertiesPersister = new DefaultPropertiesPersister();
            for (int i = 0; i < this.locations.length; i++) {
                Resource location = this.locations[i];
                if (logger.isInfoEnabled()) {
                    logger.info("Loading properties file from " + location);
                }
                InputStream is = null;
                try {
                    is = location.getInputStream();
//③加载密钥
Key key = DESEncryptUtil.getKey(keyLocation.getInputStream());
//④对属性文件进行解密
is = DESEncryptUtil.doDecrypt(key, is);
//⑤将解密后的属性流装载到props中
                    if (fileEncoding != null) {
                        propertiesPersister.load(props, new InputStreamReader(is, fileEncoding));
                    } else {
                        propertiesPersister.load(props, is);
                    }
                } finally {
                    if (is != null) is.close();
                }
            }
        }
    }
}
}

 

 

加密解密工具类DESEncryptUtil  
    对文件进行对称加密的算法很多,一般使用DES对称加密算法,因为它速度很快,破解困难,DESEncryptUtil不但提供了DES解密功能,还提供了DES加密的功能,因为属性文件在部署前必须经常加密: 
    图 2 加密解密工具类

 

package com.baobaotao.place;
…
public class DESEncryptUtil {
    public static Key createKey() throws NoSuchAlgorithmException {//创建一个密钥
        Security.insertProviderAt(new com.sun.crypto.provider.SunJCE(), 1);
        KeyGenerator generator = KeyGenerator.getInstance("DES");
        generator.init(new SecureRandom());
        Key key = generator.generateKey();
        return key;
    }
    public static Key getKey(InputStream is) {
        try {
            ObjectInputStream ois = new ObjectInputStream(is);
            return (Key) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
    private static byte[] doEncrypt(Key key, byte[] data) {//对数据进行加密
        try {
            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] raw = cipher.doFinal(data);
            return raw;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
    public static InputStream doDecrypt(Key key, InputStream in) {//对数据进行解密
        try {
            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, key);
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            byte[] tmpbuf = new byte[1024];
            int count = 0;
            while ((count = in.read(tmpbuf)) != -1) {
                bout.write(tmpbuf, 0, count);
                tmpbuf = new byte[1024];
            }
            in.close();
            byte[] orgData = bout.toByteArray();
            byte[] raw = cipher.doFinal(orgData);
            ByteArrayInputStream bin = new ByteArrayInputStream(raw);
            return bin;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
    public static void main(String[] args) throws Exception {//提供了Java命令使用该工具的功能
        if (args.length == 2 && args[0].equals("key")) {// 生成密钥文件
            Key key = DESEncryptUtil.createKey();
            ObjectOutputStream oos = new ObjectOutputStream(
                    new FileOutputStream(args[1]));
            oos.writeObject(key);
            oos.close();
            System.out.println("成功生成密钥文件。");
        } else if (args.length == 3 && args[0].equals("encrypt")) {//对文件进行加密
            File file = new File(args[1]);
            FileInputStream in = new FileInputStream(file);
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            byte[] tmpbuf = new byte[1024];
            int count = 0;
            while ((count = in.read(tmpbuf)) != -1) {
                bout.write(tmpbuf, 0, count);
                tmpbuf = new byte[1024];
            }
            in.close();
            byte[] orgData = bout.toByteArray();
            Key key = getKey(new FileInputStream(args[2]));
            byte[] raw = DESEncryptUtil.doEncrypt(key, orgData);
            file = new File(file.getParent() + "\\en_" + file.getName());
            FileOutputStream out = new FileOutputStream(file);
            out.write(raw);
            out.close();
            System.out.println("成功加密,加密文件位于:"+file.getAbsolutePath());
        } else if (args.length == 3 && args[0].equals("decrypt")) {//对文件进行解密
            File file = new File(args[1]);
            FileInputStream fis = new FileInputStream(file);
            Key key = getKey(new FileInputStream(args[2]));
            InputStream raw = DESEncryptUtil.doDecrypt(key, fis);
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            byte[] tmpbuf = new byte[1024];
            int count = 0;
            while ((count = raw.read(tmpbuf)) != -1) {
                bout.write(tmpbuf, 0, count);
                tmpbuf = new byte[1024];
            }
            raw.close();
            byte[] orgData = bout.toByteArray();
            file = new File(file.getParent() + "\\rs_" + file.getName());
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(orgData);
            System.out.println("成功解密,解密文件位于:"+file.getAbsolutePath());
        }
    }
}    

 

    解密工作主要涉及到两个类Cipher和Key,前者是加密器,可以通过init()方法设置工作模式和密 钥,在这里,我们设置为解密工作模式:Cipher.DECRYPT_MODE。Cipher通过doFinal()方法对字节数组进行加密或解密。关于 加密,解密更详细的知识,感兴趣的读者可以参阅相关的文章。

    属性文件加密解密工具类使用 
    要完成属性文件的加密工作,首先,必须获取一个密钥文件,然后才能对明文的属性文件进行加密。如果需要调整属性文件的信息,你必须执行相反的过程,即用密钥对加密后的属性文件进行解密,调整属性信息后,再将其加密。
DESEncryptUtil 工具类可以完成以上所提及的三个工作:
 生成一个密钥文件
java com.baobaotao.DESEncryptUtil key D:\key.dat
第一个参数为key,表示创建密钥文件,第二个参数为生成密钥文件的保存地址。
 用密钥文件对属性文件进行加密
java com.baobaotao.DESEncryptUtil encrypt d:\test.properties d:\key.dat
第一个参数为encrypt,表示加密,第二个参数为需要加密的属性文件,第三个参数为密钥文件。如果加密成功,将生成en_test.properties的加密文件。
 用密钥文件对加密后的属性文件进行解密
java com.baobaotao.DESEncryptUtil decrypt d:\test.properties d:\key.dat
第一个参数为decrypt,表示解密,第二个参数为需要解密的属性文件,第三个参数为密钥文件。如果加密成功,将生成rs_test.properties的解密文件。 

    在Spring中配置加密属性文件 
    假设我们通过DESEncryptUtil 工具类创建了一个key.bat密钥,并对car.properties属性进行加密,生成加密文件en_car.properties。下面,我们通过 DecryptPropertyPlaceholderConfigurer增强类进行配置,让Spring容器支持加密的属性文件: 
    假设我们通过DESEncryptUtil 工具类创建了一个key.bat密钥,并对car.properties属性进行加密,生成加密文件en_car.properties。下面,我们通过 DecryptPropertyPlaceholderConfigurer增强类进行配置,让Spring容器支持加密的属性文件:

 

<bean class="com.baobaotao.place.DecryptPropertyPlaceholderConfigurer"> ①
    <property name="locations">
        <list>
            <value>classpath:com/baobaotao/en_car.properties</value>
        </list>
    </property>
    <property name="keyLocation" value="classpath:com/baobaotao/key.dat" />
    <property name="fileEncoding" value="utf-8" />
</bean>
<bean id="car" class="com.baobaotao.place.Car"> ②
    <property name="brand" value="${brand}" />
    <property name="maxSpeed" value="${maxSpeed}" />
    <property name="price" value="${price}" />
</bean>

 

 

    注意①处的配置,我们使用自己编写的DecryptPropertyPlaceholderConfigurer替代Spring的 PropertyPlaceholderConfigurer,由于前者对属性文件进行了特殊的解密处理,因此②处的car Bean也可以引用到加密文件en_car.properties中的属性项。

 

 

分享到:
评论

相关推荐

    spring 启动时加载不同的文件

    扩展PropertyPlaceholderConfigurer实现动态加载** - **自定义配置类**: 创建一个名为`GollfPropertyPlaceholderConfigurer`的类,继承自`PropertyPlaceholderConfigurer`。 - **重写processProperties方法**: - ...

    Spring中PropertyPlaceholderConfigurer的使用

    PropertyPlaceholderConfigurer 的扩展应用 ----------------------------------- PropertyPlaceholderConfigurer 不仅可以用于加载 Properties 文件,还可以用于实现其他功能,例如属性文件加密解密等。在后续的...

    Spring PropertyPlaceholderConfigurer配置文件加载器集成ZooKeeper来实现远程配置读取

    将ZooKeeper与`PropertyPlaceholderConfigurer`结合,可以实现远程配置读取,使得系统能在运行时动态获取和更新配置,增强了系统的健壮性和扩展性。 实现这个功能,首先需要在ZooKeeper上创建一个节点,用于存放...

    4.Spring应用扩展.pptx

    例如,可以将数据库连接信息写入属性文件,如`database.properties`,然后在Spring配置文件中使用`PropertyPlaceholderConfigurer`引入属性文件。这样,我们就可以在XML配置中使用`${...}`的方式来引用属性值,如`${...

    SPRING:bean配置properties

    通过上述配置,我们可以看到`PropertyPlaceholderConfigurer`不仅提供了强大的配置文件管理能力,还具备高度的灵活性和扩展性,是Spring框架中不可或缺的一部分。合理运用这一机制,能够极大地提升代码的可维护性和...

    服务器扩展:添加服务器占位符

    Spring允许我们在配置文件中使用 `${...}` 来引用占位符,然后通过PropertyPlaceholderConfigurer 或者 @Value 注解来解析这些占位符,并在运行时替换为实际值。 例如,如果你有一个名为 `application.properties` ...

    Spring 容器后处理器

    BeanFactoryPostProcessor接口是Spring框架提供的一个接口,用于扩展Spring容器的功能。该接口只有一个方法`postProcessBeanFactory`,该方法在容器加载完成之后被调用,可以用来改变容器中的配置或做一些初始化工作...

    SSM 读取properties文件

    在Java开发领域,尤其是SSM(Spring、SpringMVC、MyBatis)框架的使用中,配置文件的管理是...通过`PropertyPlaceholderConfigurer`和`@Value`注解,我们可以轻松地管理配置信息,使项目结构更加清晰,易于扩展和维护。

    spring-property-annotations:Spring 2.5.x 组件的带注释的配置属性

    #Spring 属性注释扩展的 PropertyPlaceHolderConfigurer 使用注解将配置属性注入到 Spring 组件中。 注意:Spring 3 现在支持使用 @Value 注释的容器的。 该项目仅用于 Spring 2.5.x 支持。 ##入门Spring房产注解...

    第十九章 Spring Environment 抽象(Environment Abstraction)1

    它不仅包含了对传统占位符处理的支持,如`PropertyPlaceholderConfigurer`,还引入了更强大的`PropertySource`概念,允许从多种来源加载配置属性,如XML文件、属性文件、系统环境变量、JNDI等。 2. **Spring ...

    在Spring中使用加密外部属性文件

    Spring默认的`PropertyPlaceholderConfigurer`不直接支持加密的属性文件,但它提供了扩展点,允许我们自定义实现来处理加密后的属性。以下是一种实现方式: 1. 创建一个自定义的`PropertyPlaceholderConfigurer`...

    属性占位符配置器

    ### 属性占位符配置器:Spring框架中的高级配置机制 #### 一、概念解析 在Spring框架中,**属性占位符配置器**(Property Placeholder ...理解和掌握这一特性,对于构建稳定、高效和可扩展的应用系统具有重要意义。

    spring读取properties

    在Spring框架中,读取和使用Properties文件是一种常见的配置方式,尤其当涉及到数据库连接、环境变量等需要在运行时...掌握Spring读取Properties文件的方法,不仅能够提升开发效率,还能增强应用的灵活性和可扩展性。

    Cloud Native Java

    区分环境配置与代码配置,使用Spring框架中的PropertyPlaceholderConfigurer、Environment抽象与@Value注解,以及profiles来适应不同的运行环境和测试环境。 5. 集中式、日志化配置 以Spring Cloud为基础,介绍了...

    spring使用属性文件

    属性文件通常以`.properties`为扩展名,例如`application.properties`或`database.properties`。文件中的键值对以等号`=`分隔,如`key=value`,并且每行代表一个独立的配置项。 2. **加载属性文件** Spring提供`...

    Spring项目application.xml配置文件加解密

    `EncryptPropertyPlaceholderConfigurer.java`就是这样一个类,它扩展了Spring的`PropertyPlaceholderConfigurer`,并覆盖了`resolvePlaceholder`方法来处理加密的属性值。 ```java import org.springframework....

    掌握Spring设计模式:Java工程师必备指南

    此外,`BeanFactoryPostProcessor`接口允许自定义扩展,如`PropertyPlaceholderConfigurer`用于处理配置文件中的占位符。 2. **工厂方法**:Spring提供的`FactoryBean`接口是工厂方法模式的体现。它使得我们能够...

    Spring源码学习四:BeanDefinition装载前奏曲1

    例如,`PropertyPlaceholderConfigurer`就是在这里处理属性占位符的替换。 `registerBeanPostProcessors(beanFactory)`注册了BeanPostProcessors,这些处理器会在Bean初始化时介入,执行额外的操作,如AOP代理、...

Global site tag (gtag.js) - Google Analytics