扩展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提供对外部属性文件的支持,为了使用加密的属性文件,咱们就须要分析该类的工做机理,再进行改造。因此咱们先来了解一下该类的结构: 数组
编写支持加密属性文件的实现类
经过以上分析,咱们设计一个支持加密属性文件的加强型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
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中的属性项。