基于spring的placeholder思路处理配置信息敏感信息加密解密的实践
基于Spring的placeholder处理思路,实现系统配置信息敏感信息的加密解密处理。
我们的处理方案,是基于类org.springframework.beans.factory.config.PropertiesFactoryBean进行重写,嵌入密文信息的解密逻辑,灵活处理各种敏感信息的加解密,而且加解密算法,可以根据需要自己灵活设计。
1. 首先,设计敏感信息的加解密算法程序,这里,就基于JDK自带的工具,基于AES算法进行加密encrypt和解密dencrypth操作。
package com.tk.robotbi.core.engine; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex; /**
* @author shihuc
* @date 2018年3月8日 上午9:20:55
*
* 通过JDK自带的AES加解密方法,对系统配置参数需要防范的信息进行处理
*
*/
public class JdkAesHelper { private static String ALGORITHM = "AES";
private static String TRANSFORMATION = "AES/ECB/PKCS5Padding";
private static int KEY_SIZE = ; /*
* 这里的seed值,有点类似加密中的干扰项,使得密码更加不容易破解,可以当作私钥信息使用,即不知道这个信息,AES解密也无法进行。
*/
private static String SEED = "xxxxxxxx***************"; public static String doEncrypt(String toEnc, String seed) {
// 生成密钥
KeyGenerator keyGenerator = null;
try {
keyGenerator = KeyGenerator.getInstance(ALGORITHM);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
System.out.println(keyGenerator.getProvider());
/*
* 指定key的长度为128位
* 给定种子作为随机数初始化出。若不指定种子信息,则秘钥生产器初始化将会完全随机,最后无法解密
*/
SecureRandom random = null;
try {
random = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
random.setSeed(seed.getBytes());
keyGenerator.init(KEY_SIZE, random);
SecretKey secretKey = keyGenerator.generateKey();
byte[] bytesKey = secretKey.getEncoded(); // key转换
SecretKeySpec key = new SecretKeySpec(bytesKey, ALGORITHM); // 加密
Cipher cipher = null;
byte[] result = null;
try {
cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, key);
result = cipher.doFinal(toEnc.getBytes());
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
}
return Hex.encodeHexString(result);
} public static String doDecrypt(String toDec, String seed){
// 生成密钥
KeyGenerator keyGenerator = null;
try {
keyGenerator = KeyGenerator.getInstance(ALGORITHM);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
System.out.println(keyGenerator.getProvider());
/*
* 指定key的长度为128位
* 给定种子作为随机数初始化出。若不指定种子信息,则秘钥生产器初始化将会完全随机,最后无法解密
*/
SecureRandom random = null;
try {
random = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
random.setSeed(seed.getBytes());
keyGenerator.init(KEY_SIZE, random);
SecretKey secretKey = keyGenerator.generateKey();
byte[] bytesKey = secretKey.getEncoded(); // key转换
SecretKeySpec key = new SecretKeySpec(bytesKey, ALGORITHM);
// 解密
Cipher cipher = null;
byte[] result = null;
try {
cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, key);
byte rawHex[] = Hex.decodeHex(toDec);
result = cipher.doFinal(rawHex);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
} catch (DecoderException e) {
e.printStackTrace();
}
return new String(result);
} public static void main(String args[]){
String inPass = "二师兄到底是帅啊!";
System.out.println("加密之前:" + inPass);
String ouPass = JdkAesHelper.doEncrypt(inPass, SEED);
System.out.println("加密之后:" + ouPass);
String revPass = JdkAesHelper.doDecrypt(ouPass, SEED);
System.out.println("解密之后:" + revPass);
} /**
* @return the sEED
*/
protected static String getSEED() {
return SEED;
}
}
2. 接下来,就是PropertiesFactoryBean类的重写逻辑
package com.tk.robotbi.core.engine; import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties; import javax.annotation.PostConstruct; import org.springframework.beans.factory.config.PropertiesFactoryBean; /**
* @author shihuc
* @date 2018年3月8日 上午8:58:23
*
* 对系统配置参数,里面涉及到的加密了的信息,在实际使用之前,进行解密恢复明文使用,实现软件包静态传递的时候,不至于泄露重要的敏感信息。例如数据库的密码。
* 注意:1. 此方法是基于PropertiesFactoryBean的扩展,方便配置文件里的参数,通过注解的方式在java文件中直接使用。
* 2. 推荐使用此种方法。
*/
public class MyPropertiesFactoryBean extends PropertiesFactoryBean { /*
* 需要解密的参数列表
*/
private List<String> decryptParams; @PostConstruct
public void init(){
if(decryptParams == null){
try {
throw new Exception("parameter container initialization error");
} catch (Exception e) {
e.printStackTrace();
}
}
} /**
* 重写属性加载函数mergeProperties(),在这个函数里面,将待解密的参数找出来,然后进行指定的解密函数操作。
* 注意: 这里是静态的,默认都用一种解密算法进行,后期可以灵活的配置,实现不同的参数采用不同的解密算法,或者算法可以配置。
*/
@Override
protected Properties mergeProperties() throws IOException {
Properties result = super.mergeProperties(); /*
* 将配置过程中带入的信息首尾空格去掉,否则导致解密失败
*/
List<String> params = new ArrayList<String>();
for(String param: this.decryptParams){
String newParam = param.trim();
params.add(newParam);
} Enumeration<?> keys = result.propertyNames();
while (keys.hasMoreElements()) {
String key = (String)keys.nextElement();
String value = result.getProperty(key);
if (params.contains(key) && null != value) {
result.remove(key);
value = JdkAesHelper.doDecrypt(value.trim(),JdkAesHelper.getSEED());
result.setProperty(key, value);
}
}
return result;
} /**
* @return the decryptParams
*/
public List<String> getDecryptParams() {
return decryptParams;
} /**
* @param params the decryptParams to set
*/
public void setDecryptParams(List<String> params) {
this.decryptParams = params;
}
}
3. 最后,就是spring的配置文件的修改
默认的基于PropertiesFactoryBean的配置信息如下:
<bean id="configRealm" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="locations">
<list>
<value>classpath:conf/jdbc.properties</value>
<value>classpath:conf/mongo.properties</value>
<value>classpath:conf/redis.properties</value>
<value>classpath:conf/params.properties</value>
<value>classpath:conf/elasticsearch.properties</value>
<value>classpath:conf/fileUpload.properties</value>
</list>
</property>
</bean>
改用重写后的PropertiesFactoryBean的配置信息如下:
<bean id="configRealm" class="com.tk.robotbi.core.engine.MyPropertiesFactoryBean">
<property name="decryptParams">
<list>
<value>mongo.write1.password </value>
<value>mongo.read1.password </value>
<value>mongo.write2.password</value>
<value>mongo.read2.password</value>
</list>
</property>
<property name="locations">
<list>
<value>classpath:conf/jdbc.properties</value>
<value>classpath:conf/mongo.properties</value>
<value>classpath:conf/redis.properties</value>
<value>classpath:conf/params.properties</value>
<value>classpath:conf/elasticsearch.properties</value>
<value>classpath:conf/fileUpload.properties</value>
</list>
</property>
</bean>
基于上述几步之后,就需要将配置文件里面出现在decryptParams列表的参数用加密后的信息写入相应的配置文件,这里,我们将mongo数据库的密码进行加密写入mongo.properties,有两组的读写库密码。运行程序,启动正常,数据能够连接上。一切ok。
这里,需要注意的有下面几点:
1. spring的配置文件中,参数值后面或者前面的空格,spring加载参数的时候,是不会给trim掉的,必须自己在应用逻辑中处理。
2. spring的配置文件中,bean的list类型的成员变量,类型不同,配置方式不同。
基本类型的,可以采用如下的形式配置:
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
若bean的list类型成员变量,不是基本类型,而是具体的bean,那么需要采用下面的形式配置:
<list>
<ref bean="bean1"/>
<ref bean="bean2"/>
<ref bean="bean3"/>
</list>
3. spring的配置文件中,list的类型的参数配置,在bean的定义中,成员变量,可以定义成数组,也可以定义成List。
例如本加解密案例中,MyPropertiesFactoryBean的实现过程中,private List<String> decryptParams;的定义,可以改成private String[] decryptParams;效果是一样的。只要注意响应的setter和getter方法做适当的调整即可。
4. 继承PropertiesFactoryBean,重写函数mergeProperties(),与继承org.springframework.beans.factory.config.PropertyPlaceholderConfigurer然后重写processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)都可以达到类似的效果。只是建议采用继承PropertiesFactoryBean的方式,方便参数通过注解的形式在java程序中直接引用参数值。
不管是继承PropertiesFactoryBean的方案还是继承PropertyPlaceholderConfigurer的方案,其实最终关注到的核心都是来自共同的父类PropertiesLoaderSupport中的properties的加载逻辑前后的处理。都可以通过修改mergeProperties()函数的逻辑实现。当然也可以按照各自的需要进行调整相应函数的重写逻辑。
PS:附上继承org.springframework.beans.factory.config.PropertyPlaceholderConfigurer然后重写processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)的实现逻辑:
package com.tk.robotbi.core.engine;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties; import javax.annotation.PostConstruct; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; /**
* @author shihuc
* @date 2018年3月8日 下午1:01:22
*
* 对系统配置参数,里面涉及到的加密了的信息,在实际使用之前,进行解密恢复明文使用,实现软件包静态传递的时候,不至于泄露重要的敏感信息。例如数据库的密码。
* 注意:1. 此种方法是对PropertyPlaceholderConfigurer的扩展,实现思路直接。
* 2. 较常用,但是不便于配置文件中的参数通过注解的方式在java文件中直接使用。
*
*/
public class MyPropertiesPlaceholderConfigurer extends PropertyPlaceholderConfigurer { /*
* 需要解密的参数列表
*/
private List<String> decryptParams; @PostConstruct
public void init(){
if(decryptParams == null){
try {
throw new Exception("parameter container initialization error");
} catch (Exception e) {
e.printStackTrace();
}
}
} /**
* 重写属性处理函数,在这个函数里面,将待解密的参数找出来,然后进行指定的解密函数操作。
* 注意: 这里是静态的,默认都用一种解密算法进行,后期可以灵活的配置,实现不同的参数采用不同的解密算法,或者算法可以配置。
*/
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException {
Enumeration<?> keys = props.propertyNames(); /*
* 将配置过程中带入的信息首尾空格去掉,否则导致解密失败
*/
List<String> params = new ArrayList<String>();
for(String param: this.decryptParams){
String newParam = param.trim();
params.add(newParam);
} while (keys.hasMoreElements()) {
String key = (String)keys.nextElement();
String value = props.getProperty(key);
if (params.contains(key) && null != value) {
props.remove(key);
value = JdkAesHelper.doDecrypt(value.trim(),JdkAesHelper.getSEED());
props.setProperty(key, value);
}
System.setProperty(key, value);
}
super.processProperties(beanFactoryToProcess, props);
} /**
* @return the decryptParams
*/
public List<String> getDecryptParams() {
return decryptParams;
} /**
* @param decryptParams the decryptParams to set
*/
public void setDecryptParams(List<String> decryptParams) {
this.decryptParams = decryptParams;
} }
基于spring的placeholder思路处理配置信息敏感信息加密解密的实践的更多相关文章
- Spring Boot 所有相关的配置信息
加载顺序 如上图所示,图片是从官网上截取的,这些配置信息都会加载,只不过顺序在前的会覆盖掉后面的 上图的所有配置信息都会以(key,value)的形式加载到Spring中的Environment中,也 ...
- spring(读取外部数据库配置信息、基于注解管理bean、DI)
###解析外部配置文件在resources文件夹下,新建db.properties(和数据库连接相关的信息) driverClassName=com.mysql.jdbc.Driverurl=jdbc ...
- spring mvc 中web.xml配置信息解释
在项目中总会遇到一些关于加载的优先级问题,近期也同样遇到过类似的,所以自己查找资料总结了下,下面有些是转载其他人的,毕竟人家写的不错,自己也就不重复造轮子了,只是略加点了自己的修饰. 首先可以肯定的是 ...
- 搭建Spring相关框架后,配置信息文件头部出现红色小×错误。
问题描述: 在搭建关于Spring相关框架的时候,在applicationContext.xml配置文件和servlet-mvc.xml配件文件的头部会出现一个红色的小X错误: 错误描述: Refer ...
- 基于spring的PropertySource类实现配置的动态替换
public class ConfigPropertySource extends PropertySource<Properties> implements PriorityOrdere ...
- 基于 Spring MVC 的开源测试用例管理系统以及开发自测的实践
早前公司领导提出让开发自测,测试么也做做开发.当然了,为了保证自测质量,测试用例仍需测试提供,所以为了提高开发自测的效率和质量,我们开发了捉虫记.捉虫记是一个完整的Spring MVC项目,现已开源, ...
- 实战:基于 Spring 的应用配置如何迁移至阿里云应用配置管理 ACM
最近遇到一些开发者朋友,准备将原有的Java Spring的应用配置迁移到 阿里云应用配置管理 ACM 中.迁移过程中,遇到不少有趣的问题.本文将通过一个简单的样例来还原迁移过程中遇到的问题和相关解决 ...
- Spring Boot - 配置信息后处理
最近在做项目的过程中,PSS提出配置文件中类似数据库连接需要的用户名.密码等敏感信息需要加密处理(之前一直是明文的). 为了快速完成任务,网上搜刮到jasypt包,也有相应的starter,使用方法可 ...
- 基于Spring Boot自建分布式基础应用
目前刚入职了一家公司,要求替换当前系统(单体应用)以满足每日十万单量和一定系统用户负载以及保证开发质量和效率.由我来设计一套基础架构和建设基础开发测试运维环境,github地址. 出于本公司开发现状及 ...
随机推荐
- Golang AES加密
package main import ( "crypto/aes" "crypto/cipher" "fmt" "os" ...
- http状态码301和302的区别
1.官方的比较简洁的说明: 301 redirect: 301 代表永久性转移(Permanently Moved) 302 redirect: 302 代表暂时性转移(Temporarily Mov ...
- WSDL 文档-一个简单的 XML 文档
WSDL 文档是利用这些主要的元素来描述某个 web service 的: <portType>-web service 执行的操作 <message>-web service ...
- useradd密码无效
/********************************************************************** * useradd密码无效 * 说明: * 在测试Ubu ...
- 12.double的int方
给定一个double类型的浮点数base和int类型的整数exponent.求base的exponent次方. 不要用Math.pow(double,double)哟.效率极其低下,比连成慢好多: 题 ...
- 专题--XOR之线性基
没想到xor居然和线性代数有着那么有趣的联系哎 n个数可以转化为一个上三角矩阵 (线性无关?!) 链接:https://www.nowcoder.com/acm/contest/180/D来源:牛客 ...
- window cmd-常用命令
1.常用命令 dir 文件列表 cd 改变目录md 创建目录 rd 删除目录 type 显示文件内容 fc 比较目录 attrib 修改文件属性 copy 复制文件 del 删除文件 ren 文件改名 ...
- Linux中ctrl+z,ctrl+d和ctrl+c的区别
Ctrl-c Kill foreground processCtrl-z Suspend foreground processCtrl-d Terminate input, or exit shell
- 2017.7.11 fuse工作原理
FUSE的工作原理如图所示.假设基于FUSE的用户态文件系统hello挂载在/tmp/fuse目录下.当应用层程序要访问/tmp/fuse下的文件时,通过glibc中的函数进行系统调用,处理这些系统调 ...
- django ---forms组件
forms组件 本文目录 1 校验字段功能 2 渲染标签功能 3 渲染错误信息功能 4 组件的参数配置 5 局部钩子 6 全局钩子 回到目录 1 校验字段功能 针对一个实例:注册用户讲解. 模型:mo ...