擴展PropertyPlaceholderConfigurer

擴展PropertyPlaceholderConfigurer html

  要Spring配置時,將一些重要的信息獨立到屬性文件中是比較常見的作法,Spring只支持明文存放的屬性文件,在某些場合下,咱們能夠但願對屬性文件加密保存,以保證關鍵信息的安全。經過擴展PropertyPlaceholderConfigurer,在屬性文件流加載後應用前進行解密就能夠很好地解決這個問題了。 java

在配置數據源或郵件服務器等 資源時,你能夠直接在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提供對外部屬性文件的支持,爲了使用加密的屬性文件,咱們就須要分析該類的工做機理,再進行改造。因此咱們先來了解一下該類的結構: spring

在①處,咱們經過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中的屬性項。
相關文章
相關標籤/搜索