`

Spring对属性文件加密解密应用

    博客分类:
  • SSH
阅读更多

前言
在Spring的开发中,我们在很多情况下会使用占位符引用属性文件的属性值来简化我们的系统及使我们的系统具有更高的灵活性和通用性。这种配置 方式有两个明显的好处:
1、减少维护的工作量:资源的配置信息可以多应用共享,在多个应用使用同一资源的情况下,如果资源的地址、用户名等配置信息发生了更改,你只要调整属性文件就 可以了;
2、使部署更简单: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提供对外部属性文件的支持,为了使用加密的属性文件,我们就需要分 析该类的工作机理,再进行改造。所以我们先来了解一下该类的结构(见附件1)。
    其中PropertiesLoaderSupport类有一个重要的protected void loadProperties(Properties props)方法,查看它的注释,可以知道该方法的作用是将PropertyPlaceholderConfigurer 中locations属性所定义的属性文件的内容读取到props入参对象中。这个方法比较怪,Java很少通过入参承载返回值,但这个方法就是这样。

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

 

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

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();
                }
            }
        }
    }
    }
}

 

    对locations指定的属性文件流数据进行额外的解密工作,解密后再装载到props中。比起 PropertyPlaceholderConfigurer,我们只做了额外的一件事:装载前对属性资源进行解密。

    在代码清单 2的③和④处,我们使用了一个DES解密的工具类对加密的属性文件流进行解密。
    对文件进行对称加密的算法很多,一般使用DES对称加密算法,因为它速度很快,破解困难,DESEncryptUtil不但提供了DES解密功能,还提供 了DES加密的功能,因为属性文件在部署前必须经常加密:
    代码清单 3 加密解密工具类

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 工具类可以完成以上所提及的三个工作:
1、生成一个密钥文件
java com.baobaotao.DESEncryptUtil key D:\key.dat
第一个参数为key,表示创建密钥文件,第二个参数为生成密钥文件的保存地址。
2、用密钥文件对属性文件进行加密
java com.baobaotao.DESEncryptUtil encrypt d:\test.properties d:\key.dat
第一个参数为encrypt,表示加密,第二个参数为需要加密的属性文件,第三个参数为密钥文件。如果加密成功,将生成 en_test.properties的加密文件。
3、用密钥文件对加密后的属性文件进行解密
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配置时,将一些重要的信息独立到属性文件中是比较常见的做法,Spring只支持明文存放的属性文件,在某些场合下,我们可以希望对属性文 件加密保存,以保证关键信息的安全。通过扩展PropertyPlaceholderConfigurer,在属性文件流加载后应用前进行解密就可以很好 地解决这个问题了。

分享到:
评论
3 楼 holysky 2012-03-23  
Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'car' defined in class path resource [config/spring/applicationContext-persistence.xml]: Could not resolve placeholder 'brand'
2 楼 holysky 2012-03-23  
希望能解决一下!
1 楼 holysky 2012-03-23  
这个我试了一下不能用啊 !

相关推荐

    spring配置文件加密实现

    当Spring尝试读取配置文件时,实际上是在与加密文件系统交互,从而实现了配置文件的自动加密解密。 在实际应用中,我们还需要考虑一些其他因素。例如,为了防止密钥丢失导致的数据不可用,可以使用密钥管理系统来...

    Spring MVC请求参数与响应结果全局加密和解密详解

    Spring MVC请求参数与响应结果全局加密和解密详解 在本文中,我们将详细介绍Spring MVC请求参数与响应结果全局加密和解密的相关知识点,包括请求参数的加密和解密、响应结果的加密和解密、ContentType的处理等。 ...

    SpringBoot接口加密解密统一处理

    为了保护敏感信息,我们需要对接口的请求参数和响应结果进行加密解密处理。在本文中,我们将深入探讨如何在SpringBoot中实现接口的加密解密统一处理,以及如何通过自定义注解来灵活控制加密解密的执行。 首先,我们...

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

    这个子类需要添加解密逻辑,以便在Spring容器初始化时读取加密属性文件,解密后再进行属性替换。 2. 在自定义的`EncryptedPropertyPlaceholderConfigurer`中,你可以添加一个方法来处理加密的属性值。例如,可以...

    json接口的spring实例(含httpclient、加密解密)

    本示例将围绕一个基于Spring框架实现的JSON接口,结合HTTPClient库进行网络通信,并利用DES(Data Encryption Standard)加密解密技术确保数据安全。 首先,我们来看JSON接口的实现。Spring框架提供了丰富的支持来...

    基于Spring Boot的通用API字段加密解密Starter插件设计源码

    该源码是一款基于Spring Boot的通用API字段加密解密Starter插件,包含49个文件,其中包括33个Java源文件、5个YAML配置文件、2个JPG图片文件、1个Git忽略文件、1个配置文件、1个LICENSE文件、1个Markdown文件、1个PNG...

    Spring数据库连接等配置加密

    `propertyConfigurer`则告诉Spring使用解密器来处理配置文件中的加密属性。 最后,关于`junfeng`这个文件,它可能是作者在文章中提到的一种特定的加密方式或者一个工具的名称。由于没有具体的文件内容,这里无法给...

    jasypt加密解密工具含源码有界面

    提供的源码中可能包含了Jasypt的示例应用,以及一个图形界面,便于测试和验证加密解密过程。通过阅读源码,我们可以更深入地理解Jasypt的工作原理,以及如何在实际项目中灵活运用。 六、结论 Jasypt作为Java加密库...

    java使用AES加密/解密文件

    在IT行业中,安全是至关...以上就是关于“java使用AES加密/解密文件”的详细说明,涵盖了生成密钥、加密文件以及解密文件的核心步骤。通过理解这些知识点,开发者可以有效地保护数据的安全,防止未经授权的访问和篡改。

    Md5加密解密

    Md5加密解密

    前端加密登录,后端解密,aes加密

    总的来说,前端加密登录是通过在客户端对敏感信息进行加密,然后在服务器端解密,从而提高用户数据的安全性。对称加密,特别是AES,因其高效性和安全性,成为实现这一目标的常用工具。然而,实际应用中还需要考虑...

    Spring Boot 实现配置文件加解密原理

    通过实现 EnvironmentPostProcessor 接口,可以对 Spring Boot 应用程序的配置文件进行加密和解密。 Spring Boot 的配置文件通常是以 Properties 文件或 YML 文件的形式存在的,而这些文件中可能包含敏感信息,...

    RSA 加密程序 Version 1.5(支持文件、文本加密解密, 支持多种语言)

    文件包括源代码和安装包,源代码里包括自定义的RSA加密解密模型,方便复用。 程序采用了经典的MVC的设计方案,将底层的RSA加密解密功能封装为单独的组件,并定义了RSA的操作模型,提供视图和底层模型进行交互的外观...

    java aes128/256 对称加密解密,rsa对称加密解密验签实现

    支持任何形式的aes加密,文件,字符,字节等,内含php代码,可以与php平台实现aes加密,而且包含rsa非对称加密签名验签实现,都是对字节数组的加密解密,签名验签,支持多种形式的的,可以将待操作的对象转换成字节...

    AES加密解密

    本项目涉及使用AES进行数据加密解密,同时结合了Eclipse开发工具、MySQL数据库以及Spring MVC、Spring和MyBatis框架,构建了一个具有权限管理功能的应用。 首先,AES加密是一种块加密算法,它基于替换和置换的过程...

    SpringBoot控制器统一的响应体加密与请求体解密的注解处理方式

    为了保护数据传输的安全,通常需要对控制器返回的响应体进行加密,并确保接收到的请求体能够正确解密。在本主题中,我们将探讨如何利用自定义注解来实现SpringBoot中统一的响应体加密和请求体解密,支持MD5、SHA、...

    Spring中jdbc.properties属性文件进行密文处理

    这是一个简单的案例用来实现Spring中jdbc.properties属性文件进行密文处理,代码中的学生实体自己根据需要在数据库中进行创建

    使用jasypt-1.5加密Spring的db属性文件

    jasypt-1.5是一个Java加密工具,它可以对Spring的db属性文件进行加密,本文将介绍如何使用jasypt-1.5来加密Spring的db属性文件。 知识点 1. jasypt-1.5的安装 首先,需要下载jasypt-1.5的工具包,下载地址为...

    Spring Cloud之 Config 中 配置文件的加密与解密

    JCE补丁下载

    SpringBoot控制器统一的响应体编码/加密与请求体解密的注解处理方式,支持MD5/SHA/AES/DES/RSA

    - 对于对称加密如AES和DES,密钥需要安全存储,通常在SpringBoot应用中可以配置为环境变量或属性文件中的敏感信息。 - 对于非对称加密如RSA,需要分别管理公钥和私钥,公钥可以公开,私钥必须保密。 5. **集成到...

Global site tag (gtag.js) - Google Analytics