Spring对外部属性文件指定的某个属性进行加密、解密

[From] http://blog.csdn.net/ethanq/article/details/7333897


在我们开发当中,经常会用到spring框架来读取属性文件的属性值,然后使用占位符引用属性文件的属性值来简化配置以及使配置具有更高的灵活性和通用性。

   如下面的属性配置文件:db.properties 

   #数据库配置
   db.driver=org.postgresql.Driver
   db.url=jdbc\:postgresql\://10.166.176.127\:5432/test
   db.username=ivsadmin
   db.password=123456
   db.name=ivs


   applicationContext.xml文件

[html]  view plain  copy
  1. <context:property-placeholder location="classpath:db.properties" />  
  2.    
  3.    <bean id="dataSource"  
  4. class="com.mchange.v2.c3p0.ComboPooledDataSource"  
  5. destroy-method="close">  
  6. <property name="driverClass" value="${db.driver}" />  
  7. <property name="jdbcUrl" value="${db.url}" />  
  8. <property name="user" value="${db.username}" />  
  9. <property name="password" value="${db.password}" />  
  10. <property name="checkoutTimeout" value="3000" />  
  11.    </bean>  

对于一些敏感的属性值,例如:密码属性。为了达到安全目的,我们一般会将密码进行加密。
可能希望用户看到db.properties是这样的:
#数据库配置
db.driver=org.postgresql.Driver
db.url=jdbc\:postgresql\://10.166.176.127\:5432/ivs
db.username=ivsadmin
db.password={SMC}sYNzVKgIhOprkdGhCyt81w==
db.name=ivs

这里可以看到密码属性值是加密过的,其它的属性值不变,这样就达到安全目的。这里采用的是java的3DES加密,在前面的文章中 3DES加密、解密工具类 已经有介绍了

下面开始分析下我们的需求:
  在Spring中担负对外在化应用参数的配置的是PropertyPlaceholderConfigurer和PropertyOverrideConfigurer对象,PropertyPlaceholderConfigurer实现了BeanFactoryPostProcessor接口,它能够对<bean/>中的属性值进行外在化管理。
  就像这样:

[html]  view plain  copy
  1. <bean id="propertyConfigurer1" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
  2. <property name="locations">  
  3.     <value>WEB-INF/classes/db.properties</value>  
  4. </property>  
  5.   </bean>  

为简化PropertyPlaceholderConfigurer的使用,Spring提供了<context:property-placeholder/>元素,像applicationContext.xml文件中这样:<context:property-placeholder location="classpath:db.properties" />
这里就很清楚了,我们只要继承PropertyPlaceholderConfigurer对象,重写PropertiesLoaderSupport接口的loadProperties方法,就可以对外部属性文件的属性值进行相关的操作了

明白了需求,下来开始我们的实现代码:

DecryptPropertyPlaceholderConfigurer.java

[java]  view plain  copy
  1. import java.io.File;  
  2. import java.io.FileOutputStream;  
  3. import java.io.IOException;  
  4. import java.io.InputStream;  
  5. import java.io.InputStreamReader;  
  6. import java.util.Properties;  
  7.    
  8. import org.apache.commons.lang.StringUtils;  
  9. import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;  
  10. import org.springframework.core.io.Resource;  
  11.    
  12. import com.huawei.smc.commons.constants.CommonContants;  
  13.    
  14. /** 
  15.  * <一句话功能简述> 
  16.  *  
  17.  * @author  hKF44803 
  18.  * @version  [版本号, 2011-12-6] 
  19.  * @see  [相关类/方法] 
  20.  * @since  [产品/模块版本] 
  21.  */  
  22. public class DecryptPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer  
  23. {  
  24.     private Resource[] locations;  
  25.        
  26.     private DecryptPropertiesPersister propertiesPersister = new DecryptPropertiesPersister();  
  27.        
  28.     private String fileEncoding = "utf-8";  
  29.        
  30.     private boolean ignoreResourceNotFound = false;  
  31.        
  32.     /** 
  33.      * {@inheritDoc} 
  34.      */  
  35.     @Override  
  36.     public void setLocations(Resource[] locations)  
  37.     {  
  38.         this.locations = locations;  
  39.     }  
  40.        
  41.     /** 
  42.      * {@inheritDoc} 
  43.      */  
  44.     @Override  
  45.     public void setFileEncoding(String encoding)  
  46.     {  
  47.         this.fileEncoding = encoding;  
  48.     }  
  49.        
  50.     /** 
  51.      * {@inheritDoc} 
  52.      */  
  53.     @Override  
  54.     public void setIgnoreResourceNotFound(boolean ignoreResourceNotFound)  
  55.     {  
  56.         this.ignoreResourceNotFound = ignoreResourceNotFound;  
  57.     }  
  58.        
  59.     /** 
  60.      * {@inheritDoc} 
  61.      */  
  62.     @Override  
  63.     public void loadProperties(Properties props)  
  64.         throws IOException  
  65.     {  
  66.            
  67.         // 属性文件是否为空  
  68.         if (this.locations != null)  
  69.         {  
  70.             // 循环读取属性文件  
  71.             for (int i = 0; i < this.locations.length; i++)  
  72.             {  
  73.                 Resource location = this.locations[i];  
  74.                    
  75.                 InputStream is = null;  
  76.                 FileOutputStream fos = null;  
  77.                 try  
  78.                 {  
  79.                        
  80.                     is = location.getInputStream();  
  81.                        
  82.                     // 检查文件是否是XML文件  
  83.                     if (location.getFilename().endsWith(XML_FILE_EXTENSION))  
  84.                     {  
  85.                         this.propertiesPersister.loadFromXml(props, is);  
  86.                     }  
  87.                    // 属性文件  
  88.                     else  
  89.                     {  
  90.                         this.propertiesPersister.doLoad(props, new InputStreamReader(is, this.fileEncoding));  
  91.                         String content = this.propertiesPersister.getEncryptContent();  
  92.                            
  93.                         // 查找是否存在加密标识  
  94.                         if (StringUtils.contains(content, CommonContants.DECRYPT_FLAG))  
  95.                         {  
  96.                             try  
  97.                             {  
  98.                                 File file = location.getFile();  
  99.                                    
  100.                                 fos = new FileOutputStream(file);  
  101.                                    
  102.                                 fos.write(this.propertiesPersister.getEncryptContent().getBytes());  
  103.                                 fos.flush();  
  104.                                    
  105.                             }  
  106.                             finally  
  107.                             {  
  108.                                 if (null != fos)  
  109.                                 {  
  110.                                     fos.close();  
  111.                                 }  
  112.                             }  
  113.                         }  
  114.                     }  
  115.                 }  
  116.                 catch (IOException ex)  
  117.                 {  
  118.                     if (this.ignoreResourceNotFound)  
  119.                     {  
  120.                         if (logger.isWarnEnabled())  
  121.                         {  
  122.                             logger.warn("Could not load properties from " + location + ": " + ex.getMessage());  
  123.                         }  
  124.                     }  
  125.                     else  
  126.                     {  
  127.                         throw ex;  
  128.                     }  
  129.                 }  
  130.                 finally  
  131.                 {  
  132.                        
  133.                     if (is != null)  
  134.                     {  
  135.                         is.close();  
  136.                     }  
  137.                        
  138.                 }  
  139.             }  
  140.         }  
  141.     }  
  142. }  

其中propertiesPersister变量用我们写的DefaultPropertiesPersister类来实现,DecryptPropertiesPersister.java对象

[java]  view plain  copy
  1. import java.io.BufferedReader;  
  2. import java.io.IOException;  
  3. import java.io.Reader;  
  4. import java.util.Properties;  
  5.    
  6. import org.springframework.util.DefaultPropertiesPersister;  
  7. import org.springframework.util.StringUtils;  
  8.    
  9. import com.huawei.smc.commons.constants.CommonContants;  
  10. import com.huawei.smc.commons.constants.NumberConstants;  
  11. import com.huawei.smc.commons.util.ThreeDesUtil;  
  12.    
  13. /** 
  14.  * 重载DefaultPropertiesPersister类 
  15.  *  
  16.  * @author  hKF44803 
  17.  * @version  [版本号, 2011-12-6] 
  18.  * @see  [相关类/方法] 
  19.  * @since  [产品/模块版本] 
  20.  */  
  21. public class DecryptPropertiesPersister extends DefaultPropertiesPersister  
  22. {  
  23.     // 加密后的字符串  
  24.     private String encryptContent;  
  25.        
  26.     public String getEncryptContent()  
  27.     {  
  28.         return encryptContent;  
  29.     }  
  30.        
  31.     /** 
  32.      * {@inheritDoc} 
  33.      */  
  34.     @Override  
  35.     protected void doLoad(Properties props, Reader reader)  
  36.         throws IOException  
  37.     {  
  38.         BufferedReader in = new BufferedReader(reader);  
  39.            
  40.         // 最后写入的内容  
  41.         StringBuilder sbContent = new StringBuilder();  
  42.            
  43.         // 循环读取文件  
  44.         while (true)  
  45.         {  
  46.             // 读取每一行  
  47.             String line = in.readLine();  
  48.                
  49.             // 非空检查  
  50.             if (line == null)  
  51.             {  
  52.                 break;  
  53.             }  
  54.                
  55.             // 去掉空格  
  56.             line = StringUtils.trimLeadingWhitespace(line);  
  57.                
  58.             // 读取行为空,跳出循环  
  59.             if (line.length() == 0)  
  60.             {  
  61.                 // 长度为0,换行  
  62.                 sbContent.append("\n");  
  63.                    
  64.                 continue;  
  65.             }  
  66.                
  67.             // 每行的第一个字符  
  68.             char firstChar = line.charAt(0);  
  69.                
  70.             // 第一个字符不是#和!  
  71.             if (firstChar != '#' && firstChar != '!')  
  72.             {  
  73.                 while (endsWithContinuationMarker(line))  
  74.                 {  
  75.                     String nextLine = in.readLine();  
  76.                     line = line.substring(0, line.length() - 1);  
  77.                        
  78.                     // 非空检查  
  79.                     if (nextLine != null)  
  80.                     {  
  81.                         line += StringUtils.trimLeadingWhitespace(nextLine);  
  82.                     }  
  83.                 }  
  84.                    
  85.                 // 查找等号所有位置的索引  
  86.                 int separatorIndex = line.indexOf("=");  
  87.                    
  88.                 // 没有等号  
  89.                 if (separatorIndex == -1)  
  90.                 {  
  91.                     separatorIndex = line.indexOf(":");  
  92.                 }  
  93.                    
  94.                 // 取KEY  
  95.                 String key = (separatorIndex != -1) ? line.substring(0, separatorIndex) : line;  
  96.                    
  97.                 // 取KEY的值  
  98.                 String value = (separatorIndex != -1) ? line.substring(separatorIndex + 1) : "";  
  99.                    
  100.                 // 去掉空格  
  101.                 key = StringUtils.trimTrailingWhitespace(key);  
  102.                 value = StringUtils.trimLeadingWhitespace(value);  
  103.                    
  104.                 // 将所有的属性放到持久的属性集*  
  105.                 props.put(unescape(key), unescape(value));  
  106.                    
  107.   // DB属性文件  
  108.                 if (CommonContants.DB_PASSWORD_PROPS.equals(key))  
  109.                 {  
  110.                     // 实例加密工具类  
  111.                     ThreeDesUtil desUtil = new ThreeDesUtil();  
  112.                        
  113.                     // DB密码解密  
  114.                     if (value.startsWith(CommonContants.DECRYPT_FLAG))  
  115.                     {  
  116.                         // 去掉标识  
  117.                         value = value.substring(NumberConstants.INT_5);  
  118.                            
  119.                         // 对加密的属性进行3DES解密  
  120.                         value = desUtil.decrypt(value);  
  121.                            
  122.                         // 解密的值放到props中  
  123.                         props.put(unescape(key), unescape(value));  
  124.                     }  
  125.                     // DB密码加密  
  126.                     else  
  127.                     {  
  128.                         // 加密指定的值  
  129.                         String strEncrypt = desUtil.encrypt(value);  
  130.                            
  131.                         // 加密后的值添加一个标识,区分解密、加密  
  132.                         value = CommonContants.DECRYPT_FLAG + strEncrypt;  
  133.                            
  134.                         // 加密后的行  
  135.                         line = key + CommonContants.PROPERTIES_SEPERATE + value;  
  136.                            
  137.                         sbContent.append(line + "\n");  
  138.                     }  
  139.                 }  
  140.                 // 追加其它的属性  
  141.                 else  
  142.                 {  
  143.                     sbContent.append(line + "\n");  
  144.                 }  
  145.             }  
  146.             else  
  147.             {  
  148.                 // 追加读取的注释内容  
  149.                 sbContent.append(line + "\n");  
  150.             }  
  151.         }  
  152.            
  153.         encryptContent = sbContent.toString();  
  154.     }  
  155. }  

最后需要修改下applicationContext.xml文件,如下:

[java]  view plain  copy
  1. <bean id="propertyConfigurer1" class="com.huawei.smc.commons.DecryptPropertyPlaceholderConfigurer">  
  2. <property name="locations">  
  3.     <value>WEB-INF/classes/db.properties</value>  
  4. </property>  
  5.     </bean>  
  6.    
  7.     <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">  
  8. <property name="driverClass" value="${db.driver}" />  
  9. <property name="jdbcUrl" value="${db.url}" />  
  10. <property name="user" value="${db.username}" />  
  11. <property name="password" value="${db.password}" />  
  12. <property name="checkoutTimeout" value="3000" />  
  13.    </bean>  

这样对属性的加密就完成了,Spring进行加载的完成后,属性就加密了

提示:如果在配置中有多个配置文件需要加载,并且这些属性文件不需要做任何处理,那就需要添加下面的配置:

[java]  view plain  copy
  1. <bean id="propertyConfigurer"  
  2. class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
  3. <property name="order" value="1" />    
  4.         <property name="ignoreUnresolvablePlaceholders" value="true" />  
  5. <property name="locations">  
  6. <value>WEB-INF/classes/smc.properties</value>  
  7. </property>  

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页