通过实现 PropertySourceLoader 接口,重写 Spring Boot 的 YAML 解析器 YamlPropertySourceLoader。步骤如下:
(1)自定义实现 PropertySourceLoader 接口
(2)配置解析器
我们的重点是对解析后的配置中加密字段配置进行解密,再设置回去。代码如下:
package com.hxstrive.demo.ext.YamlPropertySourceLoaderExt;
import org.springframework.beans.factory.config.YamlProcessor;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.boot.yaml.SpringProfileDocumentMatcher;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.util.ClassUtils;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.representer.Representer;
import org.yaml.snakeyaml.resolver.Resolver;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;
/**
* 对 Spring Boot 默认的 Yaml 解析器进行重写
* 对加密的配置项进行解密操作
*
* @author hxstrive.com
*/
public class YamlPropertySourceLoaderExt implements PropertySourceLoader {
/** DES 加密解密的密钥KEY */
private static final String DES_KEY_STR = "rVoO24XK^$s&VC^Gth2Dh9yWadIvsTofa*iJ4OzF_w62pDUNVt2w76u_RcGi7Vqp";
public YamlPropertySourceLoaderExt() {}
@Override
public String[] getFileExtensions() {
return new String[]{"yml", "yaml"};
}
@Override
public PropertySource<?> load(String name, Resource resource, String profile) throws IOException {
if (ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", (ClassLoader)null)) {
YamlPropertySourceLoaderExt.Processor processor = new YamlPropertySourceLoaderExt.Processor(resource, profile);
Map<String, Object> source = processor.process();
if (!source.isEmpty()) {
return new MapPropertySource(name, source);
}
}
return null;
}
private static class Processor extends YamlProcessor {
Processor(Resource resource, String profile) {
if (profile == null) {
this.setMatchDefault(true);
this.setDocumentMatchers(new DocumentMatcher[]{new SpringProfileDocumentMatcher()});
} else {
this.setMatchDefault(false);
this.setDocumentMatchers(new DocumentMatcher[]{new SpringProfileDocumentMatcher(new String[]{profile})});
}
this.setResources(new Resource[]{resource});
}
@Override
protected Yaml createYaml() {
return new Yaml(new StrictMapAppenderConstructor(), new Representer(), new DumperOptions(),
new Resolver() {
@Override
public void addImplicitResolver(Tag tag, Pattern regexp, String first) {
if (tag != Tag.TIMESTAMP) {
super.addImplicitResolver(tag, regexp, first);
}
}
});
}
public Map<String, Object> process() {
final Map<String, Object> result = new LinkedHashMap();
this.process(new MatchCallback() {
@Override
public void process(Properties properties, Map<String, Object> map) {
// 关键点,遍历解析出来的 Map,对加密配置解密
decrypt(map);
result.putAll(Processor.this.getFlattenedMap(map));
}
});
return result;
}
private void decrypt(Map<String, Object> map) {
if(null == map) {
return;
}
for(Map.Entry<String,Object> entry : map.entrySet()) {
Object value = entry.getValue();
if(value instanceof Map) {
// 递归调用
decrypt((Map<String,Object>) value);
} else if(value instanceof String) {
// 开始解密
String valueStr = (String) value;
// 解密配置格式为 ENC(密文)
if(valueStr.matches("^ENC\\(.+\\)$")) {
DESUtils desUtils = new DESUtils();
desUtils.setKey(DES_KEY_STR);
// DES 解密操作
String val = desUtils.getDecrypt(valueStr.replaceAll("ENC\\s*\\(\\s*", "")
.replaceAll("\\s*\\)", ""));
map.put(entry.getKey(), val);
}
}
}
}
}
}在 Spring Boot 项目的 resouces 目录下面创建 META-INF/spring.factories 文件,内容如下:
org.springframework.boot.env.PropertySourceLoader=\ com.hxstrive.demo.ext.YamlPropertySourceLoaderExt