目录
5.将自定义事件监听器放到SpringBoot的事件监听器列表中
对于配置文件中某些敏感信息,如:数据库连接地址、用户名、密码等,为了安全起见,在修改之后需要加密展示,可以通过自定义事件监听器的方式在项目启动时自动对配置项进行加密。
1.自定义事件监听器
因为我们是在项目启动时,即环境准备阶段对配置信息进行加密,所以我们需要自定义一个ApplicationEnvironmentPreparedEvent事件监听器。
由于我们是对配置文件进行加密,所以必须在配置文件被加载之后再处理该事件,所以实现Order接口,并将执行顺序放在ConfigFileApplicationListener之后执行。
@Configuration
public class AfterConfigListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
// ApplicationEnvironmentPreparedEvent 是加载配置文件,初始化日志系统的事件
ConfigurableEnvironment environment = event.getEnvironment();
//获取sursen.database.encry,判断是否需要加密
String encry = environment.getProperty("sursen.database.encry");
if (!StringUtils.isNull(encry) && "true".equals(encry.trim())) {
Properties pps = PropertiesUtil.getProperties();
//获取所有配置文件名称
Enumeration enumeration = pps.propertyNames();
Map<String,String> encryMap = new HashMap<>();
while (enumeration.hasMoreElements()){
String key = (String) enumeration.nextElement();
String value = pps.getProperty(key);
if(key.startsWith("sursen.database") && !"sursen.database.encry".equals(key)){
//判断字符串是否为base64,如果是则代表加密过,不再进行加密
Boolean isBase64 = StringUtils.checkBase64(value);
String encryValue = isBase64 ? value
: new String(Base64.getEncoder().encode(value.getBytes()), StandardCharsets.UTF_8);
//对密码解密后重新赋值,用以连接数据库时使用
String decValue = new String(Base64.getDecoder().decode(encryValue.getBytes()),StandardCharsets.UTF_8);
System.setProperty(key, decValue);
encryMap.put(key,encryValue);
}
}
//将加密后的配置信息回写到配置文件中
PropertiesUtil.setValues(encryMap);
}
}
@Override
public int getOrder() {
return ConfigFileApplicationListener.DEFAULT_ORDER + 1;
}
}
2.读取配置文件
public class PropertiesUtil {
private static Logger logger = Logger.getLogger(PropertiesUtil.class);
/**
* 获取Properties对象
* @return
*/
public static Properties getProperties(){
Properties properties = new LinkedProperties();
InputStream inputStream = null;
try {
//application.properties在resources目录下
inputStream = PropertiesUtil.class.getClassLoader().getResourceAsStream("application.properties");
properties.load(inputStream);
} catch (FileNotFoundException e) {
logger.error("application.properties文件未找到!");
} catch (IOException e) {
logger.error("读取application.properties出现IOException:",e);
} finally {
try {
if (null != inputStream){
inputStream.close();
}
} catch (IOException e) {
logger.error("application.properties文件流关闭出现异常");
}
}
return properties;
}
/**
* 根据key查询value值
* @param key key
* @return
*/
public static String getValue(String key){
Properties properties = getProperties();
String value = properties.getProperty(key);
return value;
}
public static void setValue(String key,String value){
Properties properties = getProperties();
properties.setProperty(key, value);
writeBackPropertyFile(properties);
}
/**
* 新增/修改数据
* @param map
*/
public static void setValues(Map<String,String> map){
Properties properties = getProperties();
map.forEach((key,value)->{
properties.setProperty(key, value);
});
writeBackPropertyFile(properties);
}
/**
* 回写配置文件
* @param properties
* 此处获取的路径是target下classes
* 这里的path是项目文件的绝对路径
* 先获取项目绝对路径:Thread.currentThread().getContextClassLoader().getResource("").getPath();
* 然后在项目路径后面拼接"properties/sysConfig.properties";
*/
private static void writeBackPropertyFile(Properties properties){
String path = Thread.currentThread().getContextClassLoader().getResource("").getPath()
+ "application.properties";
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(path);
properties.store(fileOutputStream, "注释");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != fileOutputStream){
fileOutputStream.close();
}
} catch (IOException e) {
System.out.println("application.properties文件流关闭出现异常");
}
}
}
}
3.有序获取配置文件信息
public class LinkedProperties extends Properties {
private static final long serialVersionUID = -4627607243846121965L;
// 因为LinkedHashSet有序,所以,key在调用put()的时候,存放到这里也就有序。
private final LinkedHashSet<Object> keys = new LinkedHashSet<>();
@Override
public Enumeration<Object> keys() {
return Collections.enumeration(keys);
}
/**
* 在put的时候,只是把key有序的存到{@link LinkedProperties#keys}
* 取值的时候,根据有序的keys,可以有序的取出所有value
* 依然调用父类的put方法,也就是key value 键值对还是存在hashTable里.
* 只是现在多了个存key的属性{@link LinkedProperties#keys}
*/
@Override
public Object put(Object key, Object value) {
keys.add(key);
return super.put(key, value);
}
@Override
public Set<String> stringPropertyNames() {
Set<String> set = new LinkedHashSet<>();
for (Object key : this.keys) {
set.add((String) key);
}
return set;
}
@Override
public Set<Object> keySet() {
return keys;
}
@Override
public Enumeration<?> propertyNames() {
return Collections.enumeration(keys);
}
}
4.对配置项加密并回写配置文件
此处我使用的是base64加密,判断字符串是否为base64格式请参考博客:判断字符串是否为base64编码_敲代码的哈士奇的博客-CSDN博客_判断是否是base64
5.将自定义事件监听器放到SpringBoot的事件监听器列表中
@SpringBootApplication
public class SystemManagerApplication extends SpringBootServletInitializer {
//如果是以war包的方式在Tomcat中运行springboot项目,则需要继承SpringBootServletInitializer,
//并在configure方法中将自定义事件监听器添加进去
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.listeners(new AfterConfigListener()).sources(SystemManagerApplication.class);
}
public static void main(String[] args){
SpringApplication application = new SpringApplication(SystemManagerApplication.class);
application.addListeners(new AfterConfigListener());
application.run(args);
}
}