概述
对于不敏感的属性信息,以明文形式出现在属性文件中是合适的,但是如果属性信息是数据库用户名和密码等敏感信息,一般希望以密文的方式保存。
这就要求对应用程序配置文件的某些属性进行加密,让Spring容器在读取属性文件后,在内存中对属性进行解密,然后将解密后的属性值赋给目标对象。
我们来看下 PropertyPlaceholderConfigurer的继承关系
PropertyResourceConfigurer类中有几个方法
实例
代码已托管到Github—> https://github.com/yangshangwei/SpringMaster
DES加密解密工具类
信息的加密分为对称和非对称两种方式, 前者表示加密后的信息可以解密为原值,而后者则不能根据加密后的信息还原为原值。
MD5属于非对称加密, DES属于对称加密。
先用DES对属性值进行加密,在读取到属性值时,在用DES进行解密。
DES加密解密工具类
package com.xgj.ioc.propertyplacehoderEncryption; import java.security.Key; import java.security.SecureRandom; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; public class DESUtils { private static Key key; // 指定DES加密解密用的密钥 private static String KEY_STR = "myKey"; static { try { KeyGenerator generator = KeyGenerator.getInstance("DES"); generator.init(new SecureRandom(KEY_STR.getBytes())); key = generator.generateKey(); generator = null; } catch (Exception e) { throw new RuntimeException(e); } } /** * * * @Title: getEncryptString * * @Description: 对字符串进行加密,返回BASE64编码的加密字符串 * * @param str * @return * * @return: String */ public static String getEncryptString(String str) { BASE64Encoder base64en = new BASE64Encoder(); try { byte[] strBytes = str.getBytes("UTF8"); Cipher cipher = Cipher.getInstance("DES"); cipher.init(Cipher.ENCRYPT_MODE, key); byte[] encryptStrBytes = cipher.doFinal(strBytes); return base64en.encode(encryptStrBytes); } catch (Exception e) { throw new RuntimeException(e); } } /** * * * @Title: getDecryptString * * @Description:对BASE64编码的加密字符串进行解密,返回解密后的字符串 * * @param str * @return * * @return: String */ public static String getDecryptString(String str) { BASE64Decoder base64De = new BASE64Decoder(); try { byte[] strBytes = base64De.decodeBuffer(str); Cipher cipher = Cipher.getInstance("DES"); cipher.init(Cipher.DECRYPT_MODE, key); byte[] decryptStrBytes = cipher.doFinal(strBytes); return new String(decryptStrBytes, "UTF8"); } catch (Exception e) { throw new RuntimeException(e); } } /** * * * @Title: main * * @Description: 测试方法 * * @param args * @throws Exception * * @return: void */ public static void main(String[] args) throws Exception { System.out.println(getEncryptString("cc")); System.out.println(getEncryptString("zsmart2017")); System.out.println(getDecryptString("SkR6wWI9iws=")); System.out.println(getDecryptString("lSR/mscM1NE3sM98QFjAdw==")); } }
使用密文版的属性文件
- 运行 DESUtils, 得到 用户名和密码的加密字符串
- 修改jdbc.properties
jdbc.driverClassName=oracle.jdbc.driver.OracleDriver jdbc.url=jdbc:oracle:thin:@172.25.246.11:1521:xgj jdbc.username=SkR6wWI9iws= jdbc.password=lSR/mscM1NE3sM98QFjAdw==
PropertyPlaceholderConfigurer 本身不支持密文版的属性文件,不过我们可以扩展该类,重写 String convertProperty(String propertyName, String propertyValue)方法
package com.xgj.ioc.propertyplacehoderEncryption; import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; /** * * * @ClassName: EncryptPropertyPlaceholderConfigurer * * @Description: 继承PropertyPlaceholderConfigurer,重写convertProperty方法,对属性进行解密 * * @author: Mr.Yang * * @date: 2017年8月6日 下午11:01:23 */ public class EncryptPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer { // 对应jdbc.properties中的key private String[] encryptPropNames = { "jdbc.username", "jdbc.password" }; @Override protected String convertProperty(String propertyName, String propertyValue) { if (isEncryptProp(propertyName)) { String decryptValue = DESUtils.getDecryptString(propertyValue); System.out.println("解密后的字符串:" + decryptValue); return decryptValue; } else { return propertyValue; } } /** * 判断是否是加密的属性 * * @param propertyName * @return */ private boolean isEncryptProp(String propertyName) { for (String encryptPropName : encryptPropNames) { if (encryptPropName.equals(propertyName)) { return true; } } return false; } }
EncryptPropertyPlaceholderConfigurer使用DESUtils中的方法解密加密后的字符串。
修改配置文件,引用自定义的EncryptPropertyPlaceholderConfigurer
<!-- 引入JDBC属性文件 加密--> <bean class="com.xgj.ioc.propertyplacehoderEncryption.EncryptPropertyPlaceholderConfigurer" p:location="classpath:spring/jdbc.properties" p:fileEncoding="utf-8"/>
使用自定义的属性加载器后,就无法使用context:property-placeholder属性加载配置文件了,必须使用传统的方式引用加密版的属性文件,如上
完整的配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 --> <context:component-scan base-package="com.xgj.ioc.propertyplacehoderEncryption"/> <!-- 引入JDBC属性文件 加密--> <bean class="com.xgj.ioc.propertyplacehoderEncryption.EncryptPropertyPlaceholderConfigurer" p:location="classpath:spring/jdbc.properties" p:fileEncoding="utf-8"/> <!-- 通过属性名引用属性值 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="${jdbc.driverClassName}" p:url="${jdbc.url}" p:username="${jdbc.username}" p:password="${jdbc.password}"/> <!-- 配置Jdbc模板 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dataSource" /> </beans>
测试类
package com.xgj.ioc.propertyplacehoderEncryption; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; @Component public class PropertyPlaceHoderEncryptionTest { private final static String MATCH_COUNT_SQL = " SELECT count(*) FROM temp_user " + " WHERE user_name =? and password=? "; private JdbcTemplate jdbcTemplate; @Autowired public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } /** * * * @Title: getMatchCount * * @Description: 根据用户名和密码判断用户是否存在 * * @param username * @param password * * @return: int */ public int getMatchCount(String username, String password) { return jdbcTemplate.queryForObject(MATCH_COUNT_SQL, new Object[] { username, password }, Integer.class); } /** * * * @Title: main * * @Description: 测试 * * @param args * * @return: void */ public static void main(String[] args) { // 加载Spring配置文件 ApplicationContext ctx = new ClassPathXmlApplicationContext( "classpath:com/xgj/ioc/propertyplacehoderEncryption/beans.xml"); // 获取通过注解标注的Bean PropertyPlaceHoderEncryptionTest propertyplacehoderEncryption = ctx .getBean("propertyPlaceHoderEncryptionTest", PropertyPlaceHoderEncryptionTest.class); // 调用方法 int count = propertyplacehoderEncryption.getMatchCount("xgj", "123456"); System.out.println("匹配的用户数量:" + count); } }
测试结果